tag:blogger.com,1999:blog-143433292024-03-07T06:13:41.997+01:00Lit Window Productions programmers logbookDesigning a C++ library to reduce GUI coding times up to 90%...Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.comBlogger15125tag:blogger.com,1999:blog-14343329.post-33886600475408086282013-01-02T15:04:00.001+01:002013-01-02T15:04:59.317+01:00Still alive...Just a quick "ping": development is still ongoing. See <a href="http://litwindow.sourceforge.net/">http://litwindow.sourceforge.net/</a><br />
<br />
Happy New Year<br />
<br />
HajoHajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-8188898659553098752008-03-07T16:34:00.003+01:002008-03-07T16:37:15.097+01:00wxVisualSetup 2.0 about to be releasedThis is just a short notice to let you know that wxVisualSetup 2.0 will be released shortly. The project wizard has been enhanced and wxWidgets 2.8 binaries are now part of the installer.<br /><br />Upgrade is free of charge for existing customers. You can download the release candidate from the download page. Please find the link in the order confirmation email.Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1161538872785168652006-10-22T19:41:00.000+02:002006-10-28T20:17:46.473+02:00Lit Window Library status<p class="western" lang="de-DE"><span lang="en-GB">The Lit Window Library project is definitely alive and development continues.</span></p> <h2 class="western" lang="en-GB">Abstract</h2> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">Summary: Building a database front end with Lit Window Library…</span></p> <p class="western" style="margin-bottom: 0in;" lang="en-GB">Audience: Software Developers</p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">Level: Beginner</span></p> <h2 class="western" lang="en-GB">Introduction</h2> <p class="western" style="margin-bottom: 0in;" lang="en-GB"><i>Dear Diary!</i></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><i><span lang="en-GB">I am sitting in my favorite café in Erlangen, sipping a Latte Macchiato and am very happy. Yesterday I visited my customer and showed off the latest version...</span></i></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">Yes, yesterday was a great day for me. After more than a year of development my customer began entering live business data yesterday. I sat a desk away, ready to intervene and fix any last minute crashes and show stoppers, but there were none.</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">And only today it slowly dawns on me that this is a major, major milestone </span><span lang="en-GB">for the Lit Window Library as well. No, the project is not dead. Far from it. I was simply too busy in the last months to bother with any kind of publicity. Now, as my workload hopefully lessens a little, I intend to publish more again.</span></p> <h2 class="western" lang="de-DE"><span lang="en-GB">What works *today*…</span></h2> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">I have written a lot about grand ideas, design schemes and how great all of this is going to be - once the library has actually been implemented. But things have progressed a lot since my <a title="first note over at slashdot" href="http://developers.slashdot.org/article.pl?sid=04/10/18/1118242&tid=162" target="blank_">first note over at slashdot</a> and in today's blog entry I will write only about what is working <strong>here and now</strong>.</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">The program I installed yesterday is a tool to help plan and organize business information, from financial data to individual project management issues. Different bits and pieces of information, each needing a table in a database and a form on a graphical user interface. And many interdependencies.</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">The application I wrote is built upon a (closed source) framework called ‘Mikado’, which I’ve been developing as the successor for my bug tracking tool </span><span style="color: rgb(0, 0, 255);"><u><a href="http://www.litwindow.com/buglister"><span lang="en-GB">BugLister</span></a></u></span><span lang="en-GB">. Mikado is a database front end framework which uses ODBC to connect to a database server. Its a little bit like MS-Access, written in C++ for wxWidgets and the Lit Window Library. A core component of the Mikado framework is the <i>record_form</i> control, a single C++ class that houses an arbitrary form (a wxPanel loaded from XRC), handles the user interface and load/save operations and also calls DLL plugins to allow implementation of specific business logic that goes beyond the capabilities of the framework. <em>(Now imagine Python as a script language and a decent WYSIWYG form editor... But I promised I'd write only about what is working today. ;)</em></span></p> <p class="western" style="margin-bottom: 0in;" lang="en-GB">To explain how this works, I am going to walk through the steps to add a new kind of record ‘Project’. My client develops leading-edge communication equipment. A ‘Project’ record shall describe the financial planning aspect of such a development project. It has a unique project name, start date, customer(s), planned cash flow and required development resources. Other records will reference it. Invoices, for example, will be assigned a project, so the project ‘contains’ a list of invoices and we’d like to show all invoices written for a specific project in a list on the project’s form. </p> <h2 class="western" lang="en-GB">CREATE TABLE project</h2> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">The first step is to define a database table. Here is the shortened version of the SQL statement. We are using </span><span style="color: rgb(0, 0, 255);"><u><a href="http://www.postgresql.org/"><span lang="en-GB">postgres</span></a></u></span><span lang="en-GB">, by the way, but I've tested Mikado with MS-SQL, MS-Access and Oracle as well.</span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB"> <blockquote> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">CREATE TABLE projects</span></span></span></p> <p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">(</span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">id serial NOT NULL, -- unique id</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">name varchar(32) NOT NULL, -- project name</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">start_date date, -- start of the project</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">customer int4, -- the customer paying for the project</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">status int2, -- status</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">notes text, -- some notes</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">planscript text, -- financial planning information</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">sys__parent int4, -- a parent project</span></span></span></p> <p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">CONSTRAINT pk_projects_id PRIMARY KEY (id),</span></span></span></p> <p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">-- foreign keys omitted for brevity</span></span></p> <p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">);</span></span></p></blockquote></span></span></span> <h2 style="margin-bottom: 0in;" lang="de-DE">Create the user input form for projects</h2> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><em><span lang="en-GB">Get </span><span style="color: rgb(0, 0, 255);"><u><a href="http://www.anthemion.co.uk/"><span lang="en-GB">DialogBlocks</span></a></u></span><span lang="en-GB">, if you haven’t already. It’s the best GUI designer for wxWidgets that I know and is written by Julian Smart. If you are using Visual Studio, get </span><span style="color: rgb(0, 0, 255);"><u><span lang="en-GB">wxVisualSetup</span></u></span><span lang="en-GB"> as well, as it not only integrates wxWidgets into the Visual Studio IDE. It also helps developing the Lit Window Library, because I am the one who gets the money. Okay, enough advertising ;)</span></em></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB"></span> </p> <p class="western" style="margin-bottom: 0in;" lang="en-GB">A form is actually a wxPanel with sizers and widgets inside. To create the form, I add a wxPanel to the list of documents and add a wxFlexGridSizer.</p> <h3 class="western" lang="en-GB">id</h3> <p class="western" style="margin-bottom: 0in;" lang="en-GB">I first add a wxStaticTextCtrl and set the label to “Id:”. This is the label the user sees so he knows that the number next to it is the id of the project. The id itself is read only. The user should not be able to change it, so I add another wxStaticTextCtrl next to the label. wxWidgets has one HUGE advantage over other GUIs I know, such as the MFC. In wxWidgets, every widget/control can have a name. The Lit Window Library data binding mechanism builds upon this. I give the new wxStaticTextCtrl a name: “id”. Note that the column in the database table has the same name.</p> <h3 class="western" lang="en-GB">start_date, name, notes</h3> <p class="western" style="margin-bottom: 0in;" lang="en-GB">The next field I add to my form is the project “start_date”. I add another wxStaticTextCtrl as the label and set its value to “Start date:”. The column “start_date” is of type “date”. The Mikado framework converts this to a wxDateTime object. I add a wxDatePickerCtrl to the form, next to the label, and give it the name “start_date”. Again the control has the same name as the column in the database. I repeat these steps for “name” with a wxTextCtrl singleline and “notes” with a wxTextCtrl multiline.</p> <h3 class="western" lang="de-DE"><span lang="en-GB">customer, status</span></h3> <p class="western" style="margin-bottom: 0in;" lang="en-GB">The “customer” column is an enumeration. It contains only a number, not the actual name of the customer. This number references the corresponding entry in a “customers” table in a database. We need a control that lets a user select one entry out of a list, 1 of n. That’s what a wxComboBox is for. I add one and name it “customer”. The Mikado framework does the rest. At program start, it loads the definition of the “projects” table and also resolves foreign key references. When it encounters a wxComboBox in a form, it fills the combo box with names from the “customers” table and binds the selection to the “customer” column of the “project” record. The result: the user sees all customers in the list and the current selection is read from and written to the “customer” column.</p> <p class="western" style="margin-bottom: 0in;" lang="en-GB">I add another wxComboBox for the foreign key column "status", a list with values such as “open”, “completed”, “possible”, “obsolete”.</p> <h3 class="western" lang="en-GB">automatic text completion</h3> <p class="western" style="margin-bottom: 0in;" lang="en-GB">Actually I am not using a wxComboBox here, but a custom control "lwAutoCompleteTextCtrl", part of the Mikado framework. This control works like the autocomplete text controls you see when you are using a web browser and start typing an URL in the address bar. The list of customers is much too long to be using a combo box. lwAutoCompleteTextCtrl lets you type a few characters and the list is narrowed down immediately. But the underlying mechanisms – foreign key table and current selection – are exactly the same as for wxComboBox.</p> <h3 class="western" lang="en-GB">sys__parent</h3> <p class="western" style="margin-bottom: 0in;" lang="en-GB">“sys__parent” is an interesting one. In Mikado, columns starting with “sys__” always have a special meaning. The “sys__parent” column defines a hierarchy. For table “projects”, “sys__parent” specifies the parent project of the current record, pointing to another row in the “projects” table. I wrote another custom control lwAutoCompleteTreeCtrl, which works like lwAutoCompleteTextCtrl, except that it shows a tree instead of a list. Adding the lwAutoCompleteTreeCtrl to the form lets the user enter the parent project by typing its first few letters. Or, if the user presses F4 to drop down the tree control, he can see the entire project hierarchy and select the parent project.</p> <h3 class="western" lang="en-GB">planscript</h3> <p class="western" style="margin-bottom: 0in;" lang="en-GB">This text column contains the actual planning, written in a small scripting language which we designed specifically for that purpose. The parser that handles this is contained in a plugin and reacts to events sent by the framework. I am not going into more details here and simply add a multiline wxTextCtrl for it.</p> <h3 class="western" lang="en-GB">saving the form</h3> <p class="western" style="margin-bottom: 0in;" lang="en-GB">The form (wxPanel) itself, like all the controls inside, also has a name property. I name my form “f_projects” and save everything to an .XRC file. The .XRC files are XML files containing the GUI layout. Here is a short code snipped of how you would use them in your C++ code.</p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><blockquote><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// load XRC file into memory</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">wxXmlResource::Get()->Load(filename);</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// create a wxPanel from the “f_projects” definition</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">wxPanel *panel=wxXmlResource::Get()->LoadPanel(“f_projects”);</span></span></span></span></p></blockquote><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB">This would return a wxPanel object containing all sizers and widgets and all that I designed with DialogBlocks above.</p> <h2 class="western" lang="en-GB">Registering table and form</h2> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">Now that I have a database table and an XRC form, I need to register them with the framework. To register the table, I add the value “projects” to a special table “l_types”. “l_types” contains the list of the database tables that Mikado knows about. At startup, when Mikado reads it and finds the value “projects”, it will query the database server for the definition of “projects” and build a column and key catalog for it. This is the moment when the framework discovers that column “customer” references another table and that wxComboBoxes later need to be filled with values from “customers”.</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">I also need to tell Mikado that the form “f_projects” is the right form for “project” records and I also want a human readable name for the form. To do that I add a record “f_projects”, “projects”, “Project folder” to another special table “v_views”. This table contains a list of all forms the user can choose from. The GUI shows this list on the left side of the frame, like the folders list of your email program.</span></p> <h2 class="western" lang="en-GB">Voíla…</h2> <p class="western" style="margin-bottom: 0in;" lang="en-GB">To test it I start Mikado. It shows an entry “Project folder” in the view list, which I click on. Mikado then shows me a list of records from the “projects” table in the database. I select one. Mikado loads the form “f_projects” (a wxPanel containing controls, remember?) from the XRC file, shows it on the screen, loads the record I selected from the database and fills the form with values from the record.</p> <p class="western" style="margin-bottom: 0in;" lang="en-GB"> </p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">That last part is handled entirely by </span><span style="color: rgb(0, 0, 255);"><u><a href="http://www.litwindow.com/library"><span lang="en-GB">Lit Window’s RapidUI</span></a></u></span><span lang="en-GB"> object, which I wrote so much about already. The framework calls AddWindow() to add the form to the RapidUI object. It then calls AddData() to add the record. After that the RapidUI object creates default rules: “for each data element” - search for a window element of the same name and if one exists, create a simple rule “window.element = data.element”. Together, these default rules bind all columns to their controls.</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB"></span> </p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">The code inside Mikado’s “record_form” is pretty simple, thanks to the Lit Window Library:</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB"></span> </p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><blockquote><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// load the form, init the RapidUI object</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">void record_form::prepare_for(wxString type_name, wxString form_name)</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">{</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // m_working_record is of type mikado_record</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // create a new, empty record of type ‘type_name’</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // also changes the type of m_working_record<br /><br />m_working_record.create(type_name);<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // add the data to the RapidUI mediator<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> m_rapidUI.AddData(m_working_record);<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // load the form from the XRC file<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> wxPanel *m_form=wxXmlResource::Get()->LoadPanel(form_name);<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // add the form to the RapidUI object<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> m_rapidUI.AddWindow(*m_form);<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // start the RapidUI object<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> m_rapidUI.Start();</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">}</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// load a record from the table</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">void record_form::load(long bookmark)</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">{</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // create a rowset (similar to wxDbTable)<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> mikado_rowset rows(m_connection);<br /></span></span><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />// bind the current working record to the rowset<br /></span></span><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> rows.bind(m_working_record);</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />// set the SQL where clause to load a particular record</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB"> // ([in]bookmark) is OLEDB syntax to specify a named parameter</span></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // ‘bookmark’. Mikado’s ODBC layer uses the same syntax.</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />rows.set_where(“id=?([in]bookmark)”);</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />// bind the parameter to the actual variable</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />rows.bind(bookmark, “bookmark”);</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />// open the table</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />rows.open();</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />// fetch the first entry</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // this stores the values in the m_working_record</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />rows.fetch();</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />// now tell the library that m_working_record has changed</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // this triggers the RapidUI rules system and will update</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // everything in the form</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />litwindow::NotifyChanged(m_working_record);</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">}</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">void record_form::test_project_form()</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">{</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // first prepare everything for objects of type</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> // “projects” and load the form “f_projects”</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />prepare_for(“projects”, “f_projects”);</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />// now load record with id==5</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br />load(5);</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">}</span></span></span></span></p></blockquote><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span lang="en-GB"></span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">That’s all. And the point is, I’ve written this code <b><u><i>once</i></u></b> and will never have to write it again, because it works with all kinds of records. This is the ultimate goal of the Lit Window Library, a library of building blocks at a significantly higher level of abstraction that other libraries.</span></p> <h2 class="western" lang="en-GB">High level library of building blocks</h2> <p class="western" style="margin-bottom: 0in;" lang="en-GB">The “record_form” is actually a mediator class (search google for mediator design pattern). It connects a record and a form and handles everything in between. A “record_form” can load, create and save arbitrary database records using arbitrary forms. Written once, used many times. If you need to show a single database record, use the “record_form” mediator from the library. Want to display a list of records and handle select/edit/insert/delete? Use a “select/edit/insert/delete” mediator object in combination with the “record_form” mediator. Instantiate them, connect a list control, a form and some action triggers (buttons and menu entries) for the insert/delete/save actions. Everything I have implemented so far is only a means to write generic mediator classes such as “record_form”.</p> <h2 class="western" lang="en-GB">Conclusion</h2> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">When my customers started entering project information for the first time yesterday, we hit a minor snag immediately. Some projects did not have a start date yet, but the form required an entry. To fix this, I loaded the form in DialogBlocks and set the SP_ALLOWNONE flag for the “start_date” wxDatePickerCtrl. I saved the XRC file and ran Inno Setup to create a new setup.exe. They installed it and the problem was gone. This took less than five minutes and I didn't even have to recompile the application.</span></p> <p class="western" style="margin-bottom: 0in;" lang="en-GB">They also wanted to categorize projects by region (Asia, Europe, US). While they were waiting I </p> <ul> <li> <p class="western" style="margin-bottom: 0in;" lang="en-GB">added a new column “region” to the projects table definition</p> </li><li> <p class="western" style="margin-bottom: 0in;" lang="en-GB">added a simple table “regions”</p> </li><li> <p class="western" style="margin-bottom: 0in;" lang="en-GB">added a simple form to be able to add regions and</p> </li><li> <p class="western" style="margin-bottom: 0in;" lang="en-GB">added another lwAutoCompleteTextCtrl to the projects form</p></li></ul> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">and created a new setup.exe. All of it without recompiling the application. I stress this because the chance of breaking something is a lot higher when you change actual C++ code. Mikado gives me a lot more confidence to make changes “on the fly”, while the customer is waiting.</span></p> <h2 class="western" lang="en-GB">Outlook</h2> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">I’ve saved the juiciest part for my next blog entry: showing the list of invoices attached to a project in the projects form. In that entry I will add a lwMikadoDBGridCtrl and connect its properties and actions to the main form. These "connections" use the Lit Window Library rules language and are contained in a simple text field in the XRC file. Like above, changes to that connection can be made without recompiling the application.</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">Here is the code from the “LITWINDOW_RULES” field in the XRC file. This is actual, working code, except that I have changed the identifier names because of Copyright issues.</span></p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB"></span> </p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><blockquote><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// set the invoice_grid database connection</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// to the form’s database connection</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">invoice_grid.Connection:=form.connection;</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// tell the invoice grid to reload itself</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// when the current_bookmark of the form changes</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// the invoice will no longer show all invoices,</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// it will only show invoices assigned to an object</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// ‘current_bookmark’</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">invoice_grid.ParentBookmark:=form.current_bookmark;</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">// tell invoice to save changes when the </span></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// form.IsSaving trigger fires</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">invoice_grid.SaveAction:=form.IsSaving;<br /></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// tell form to mark data as dirty when the</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// invoice_grid.IsDirty trigger fires</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">form.DirtyAction:=invoice_grid.IsDirty;</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p class="western" style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><br /></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// this is not c++ code, these are lit window</span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="de-DE"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span lang="en-GB">// library rules (or constraints), compiled and</span></span></span></span></span></p><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"><span style="font-family:Courier New,monospace;"><span style="font-size:85%;">// evaluated at runtime</span></span></span></span></p></blockquote><span style="font-family:Courier New,monospace;"><span style="font-size:85%;"> </span></span><p style="margin-bottom: 0in;" lang="en-GB"><span lang="en-GB">That’s all for now. I hope you found it interesting reading. If you did, please help spread the word and send the </span><span style="color: rgb(0, 0, 255);"><u><a href="http://litwindow.blogspot.com/"><span lang="en-GB">link to my blog</span></a></u></span><span lang="en-GB"> to your colleagues. And as always I welcome your comments. Do you want more details? Less? Are these blog entries too long to read? Comments on the library?</span></p> <p class="western" style="margin-bottom: 0in;" lang="en-GB"><br /></p> <p class="western" style="margin-bottom: 0in;" lang="en-GB">Best regards</p> <p class="western" style="margin-bottom: 0in;" lang="de-DE"><span lang="en-GB">Hajo Kirchhoff</span></p>Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com2tag:blogger.com,1999:blog-14343329.post-1161531946773544122006-10-22T17:43:00.000+02:002006-10-22T17:45:46.773+02:00New layout for my blog...Just a quick note. I've changed the layout of my blogs as my posts tend to be rather long and contain a lot of text. Things should be easier to read now, especially the code parts.Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1160687036885330622006-10-12T23:03:00.000+02:002006-10-12T23:05:07.966+02:00<H2>Lit Window Library Design Thoughts #1</H2> <H3>Abstract</H3> <P>Summary: Explains how to call C++ methods from the Lit Window Library rules system.</P> <P>Audience: C++ GUI Developer</P> <P>Difficulty: Advanced</P> <H3>Introduction</H3> <P>Hello, this is the first in a series of articles detailing the ideas behind the Lit Window Library. I want to share my thoughts about the design process and also answer questions I am getting via eMail. If you have not heard of the Lit Window Library yet, you might want to head over to <A href="http://www.litwindow.com/library">www.litwindow.com/library</A> and browse the official site. This blog keeps you informed about what is going on behind the scenes. </P> <P> </P> <P>In short: The Lit Window Library extends the decade old 'Method, Property, Event' paradigm in GUI programming with a fourth concept: 'Rules', sometimes also called 'Constraints'. A 'Constraint' is a condition, such as 'frame.title = listbox.selection', which you set at design/compile time. The Lit Window Library constraints solver will ensure that the condition will always be true and update the values - frame.title in this example - whenever a dependent value changes. This greatly reduces the amount of bookkeeping code and together with 'data adapters' (please see <A href="http://www.litwindow.com/library">www.litwindow.com/library</A>) allows creating a library of frequently used user interface mechanisms. I hope to be able to reduce GUI coding times by up to 90% while at the same time allow tight integration between the new 'rules' mechanism and traditional C++ coding methods. </P> <P> </P> <P>Which leads me to my first topic, which I received as a question via email: </P> <P> </P> <H2>Q. How do I tell the Lit Window Library to call my C++ code whenever a value changes? I want to play a sound everytime the selection in a list box changes. </H2> <P> </P> <P>Let's assume you have a list box and a frame window and you want to display the value of the current list box selection in the title of the frame. The rule would be 'my_frame.title = my_listbox.selection'. Once set, the Lit Window Library will update the frame's title automatically whenever the list box selection changes. Now let's assume you want to play a sound everytime the selection changes. Or call any other of your own C++ code. </P> <P> </P> <P><EM>Note: what follows is my design idea, which has not been implemented yet.</EM> </P> <P><EM></EM></P> <P>There are three different ways at different abstraction level to achive this. Actions, named rules and change notifications. </P> <H3> Actions </H3> <P>Actually I stole the term from Delphi. If you've ever used Delphi, you will be familiar with the concept. An action object is something that is fired by a trigger. A trigger is usually either a button or a menu event. The action calls a method. The advantage of this over other C++ libraries is simple: if you disable the action, all connected triggers will be disabled automatically. If you have, say, a 'Save As...' action and disable it, all toolbar buttons, menu entries, popup menus etc... that are connected to this action will appear disabled without any further work on your part. I like this approach a lot better than the usual OnSaveAsUpdate event where you have to query the SaveAs state for every menu entry, every button and every toolbar separately. A lot of work just to reflect the state of the application in a handful controls. </P> <P>In the Lit Window Library you create an action object and connect a C++ method to it using a 'function adapter'. A 'function adapter' is a simple piece of code that reflects the C++ method into the rules world, making the method known by name. Think of it as a language binding, much like the language binding between Python and C++. Syntax in the rule system could look like this: </P> <BLOCKQUOTE> <P>Action SaveAsAction; </P> <P>SaveAsAction.method = MyCppMethod; </P> <P>SaveAsAction = SaveAsButton + SaveAsMenu + SaveAsToolbar; </P> <P>SaveAsAction.Enabled = somedata.state == IsDirty; </P></BLOCKQUOTE> <P>You declare the object SaveAsAction, assign the C++ method "MyCppMethod" to it, connect a button, a menu entry and a toolbar button and tell the Lit Window Library system to enable the action - and thus the button, menu and toolbar, whenever somedata.state equals IsDirty. </P> <P> </P> <P>How does this help to play a sound whenever the list box selection changes? Simple. A changing selection is a trigger, just like a button that is pressed. Here is the code: </P> <BLOCKQUOTE> <P>Action PlaySoundOnSelectChanges; </P> <P>PlaySoundOnSelectChanges.method = MyCppPlaySoundMethod; </P> <P>PlaySoundOnSelectChanges = my_listbox.OnSelectionChanging; </P></BLOCKQUOTE> <P>This looks very similar to the 'OnEvent' mechanism used by GUI class libraries. Not suprising, because it is the same concept, spiced up using the action idea. PlaySoundOnSelectChanges is an action object that calls 'MyCppPlaySoundMethod' whenever it is enabled and triggered. my_listbox.OnSelectionChanging is a trigger that fires everytime the selection changes. </P> <P> </P> <P>Thats the first method to play a sound whenever the selection is changing. </P> <H3>Named rules </H3> <P>Somewhat lower in the hierarchy are named rules and rule evaluation events. The Lit Window Library uses a constraints solver to update values when dependent values change. The constraints solver uses rules as its input. You've seen examples of rules above. All of these rules where unnamed rules. The Lit Window Library rules syntax allows named rules, giving a rule or a set of rules a name by which they can be accessed. Here is the syntax: </P> <BLOCKQUOTE> <P>selection_changing: frame.title = listbox.selection; </P></BLOCKQUOTE> <P>The general syntax is {name :} rule, where the name and colon are optional. If they are omitted you are specifying an unnamed rule. You can also group several rules together and give the whole group a name. { name : } '{' rule '}'. </P> <P>The constraints solver publishes boost::signals which your code can connect to. Different signals are fired for different actions and events. One signal is the 'value(s) changing' signal. It is fired everytime the value or values of a rule or group of rules are changing. To play a sound whenever the list box selection changes, you need to write your OnCppPlaySoundMethod and connect it to the constraints solver 'value(s) changing' signal. </P> <BLOCKQUOTE> <P>RapidUI m_rapidUI; </P> <P>// init rapidui object with </P> <P>// data and window objects </P> <P>// now connect the event </P> <P>m_rapidUI.get_rule("selection_changing").value_changing.connect(boost::bind(&OnCppPlaySoundMethod)); </P></BLOCKQUOTE> <P>get_rule(string) returns the named rule object. It has a member 'value_changing', which is a boost::signal (see <A href="http://www.boost.org">www.boost.org</A> for more info). connect takes a function object which gets called everytime the signal is fired. The rest is taken care of by the constraints solver. Whenever the value of my_listbox.selection changes, the constraints solver will reevaluate the rule and will change the value of the frame.title. Before it does that, it fires the 'values_changing' signal of this rule, giving your C++ code a chance to act. Not shown here is the actual interface of the OnCppPlaySoundMethod, but the interface also allows your C++ code to change the value before it is assigned or otherwise modify the rules behaviour. This allows very complex interactions between the Lit Window Library rules system and your own C++ code and this is also one of the very important and strong points of the system: I wanted to create system that allows a programmer to switch between both world very, very easily - the rules world and the classic C++ sequential world. </P> <H3> Change notifications </H3> <P>Change notifications are at the lowest level of the abstraction. For the rules system to work, it needs to be told whenever a value changes. Values can be C++ members, in which case you - the programmer - must call litwindow::NotifyChanged(value) every time you assign a new value to the member variable. Lit Window Library predefined objects, such as Listbox and other GUI objects already do this behind the scenes everytime you call SetSelection or a user changes the selection via mouse or keyboard for example. litwindow::NotifyChanged sends the information that object 'x' at location 'y' has changed to all active constraints solvers. The constraints solvers in turn search their lists for rules that are affected by this change and reevaluate them, possibly updating other values, sending more change notifications and updating yet other values until all rules have been reevaluated and a new set of values has been assigned - or until a conflict has been found. </P> <P>These notifications are low level change notifications. They are not sent for rules, they are sent for individual objects. Like with rules you can connect your own C++ code to a change notification. </P> <BLOCKQUOTE> <P>RapidUI m_rapidUI; </P> <P>m_rapidUI.get_data("frame.title").value_changing.connect(boost::bind(&OnCppPlaySoundMethod)); </P></BLOCKQUOTE> <P>Note that I connected the OnCppPlaySoundMethod to the frames title object here. I could have connected it to my_listbox.selection. But since a change in the selection (actually a change in the value of the selection - the string value of the currently selected list box entry) will automatically change the title as well, OnCppPlaySoundMethod will still be called when the selection changes. The change is propagated to the frame.title object by the constraints system. </P> <P> </P> <P>Thats all from me for tonight. I hope you found my first public rambling on the Lit Window Library ideas interesting and perhaps you want to send me some feedback. I always like to hear other ideas, thoughts, critisism and support. </P> <P> </P> <P>Best regards </P> <P> </P> <P>Hajo Kirchhoff (<A href="http://www.litwindow.com">www.litwindow.com</A>) </P> <P> </P>Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com1tag:blogger.com,1999:blog-14343329.post-1139132369793829222006-02-05T10:24:00.000+01:002006-02-05T10:48:29.113+01:00wxWidgets Arrays in C++: one more reason to use std::vector<>In this post I'll talk a little about using std::vector<>.<br /><br />wxWidgets offers WX_DEFINE_OBJARRAY and other #defines to help you implement vectors (arrays) of your own types. If you are still using this feature, here is another reason to switch to std::vector<><br /><br />Intellisense 2005 will not work with wxArrays of your own type. Classes that use them will not be recognized in most cases. The reason is the need to include arrayimp.cpp multiple times. The wxWidgets documentation briefly mentions std::vector but goes on saying that the wxArray mechanism can be compiled with any dumb C++ compiler in the world. (http://www.wxwidgets.org/manuals/2.6.2/wx_wxarray.html)<br /><br />Well most C++ compilers by now either come with the C++ Standard Template Library included or you can use STLport, a free port of the STL, with your compiler. Almost all compilers now support templates.<br /><br />So here is my full list of reasons to throw away all WX_ARRAY_DECLARE macros and use std::vector<> instead.<br /><br /> 1. std::vector<> works fine with Intellisense, wxArray does not<br /> 2. std::vector<> is in the official C++ standard, wxArray is not<br /> 3. wxWidgets has a wxUSE_STL switch already and uses STL internally. I expect that future versions will eventually deprecate the WX_ARRAY... macro mechanism.<br /> 4. Learing std::vector<> is a great way to get started with the STL, which includes many other, extremely helpful constructs.<br /> 5. And after STL: boost. If you haven't heard about it, check it out at www.boost.org. Boost is like a supercharged Standard Template Library. Many more utilities, some easy, others difficult to learn, but once you get the hang of it, they will amaze you.<br /><br />Here is a quick starter on how to use std::vector<> for objects of type MyDirectory.<br /><blockquote><br /> struct MyDirectory {<br /> // include some reasonable definition<br /> };<br /><br /><br /> #include <vector>// note: vector, not vector.h<br /><br /> // now define a 'MyDirectory' array<br /> typedef std::vector ArrayOfDirectories;<br /><br /> ArrayOfDirectories m_my_array;<br /><br /> You can now use<br /><br /> // to append a new element to the array<br /> m_my_array.push_back(aDirectory);<br /><br /> // to access an element<br /> m_my_array[5];<br /><br /> // or with boundary check: will throw an error if vector.size()<5<br /> m_my_array.at(5);<br /><br /> // to check the size of the array<br /> m_my_array.size();<br /><br />// to iterate over array<br />for (size_t i=0; i<m_my_array.size(); ++i)<br /> do_something(m_my_array[i]);<br /><br />// to sort the array<br />sort(m_my_array.begin(), m_my_array.end());<br /><br />// to find something in the array<br />find(m_my_array.begin(), m_my_array.end(), value);<br /><br />// to clear the array<br />m_my_array.clear();<br /></blockquote><br />I prefer vector and other containers (list, map, deque, queue etc...) over their wx counterparts.<br /><br />Instead of using WX_DECLARE_OBJARRAY(ElementType, Name), write<br />typedef std::vector Name;<br /><br />#include arrayimpl and WX_DEFINE_ARRAY are no longer neccessary.<br /><br />Note that STL lacks WX_SORTED_ARRAY, but you can use std::set for that instead.<br /><br />Have fun<br /><br />HajoHajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com2tag:blogger.com,1999:blog-14343329.post-1138698381965058032006-01-31T09:15:00.000+01:002006-01-31T11:04:40.276+01:00Visual Studio 2005 Intellisense seems unreliable...I've now been working with Visual Studio C++ 2005 for a couple of weeks and I am beginning to get to know it better... and get to know its quirks. Intellisense seems buggy! Again! Overall it is surprisingly good ... as long as it works. But every now and then, usually every 5 minutes or so, it seems to stop working alltogether. And especially when I need it most such as after I have added a new method to a class.<br /><br />I found out that in fact, most of the times it is still working, but it just takes more time to update itself than I'd expect. Here is a tip that helps me keep Intellisense running:<br /><br />Immediately above the text editor window there are two comboboxes. The left one shows the scopes of the current file and the right one shows the list of methods of the scope. They are called the 'navigation bar'. This has been in Visual Studio for a long time and is very useful to navigate through the classes.<br /><br />It is a very useful indicator for Intellisense as well. The problem with Intellisense is not so much that it stops working alltogether, but that it is a lot slower than I type.<br /><br />When a definition changes, Intellisense updates itself in the background, but only during idle time. I am a fast typer. I add a declaration and then switch to the .cpp file to write the definition, typing away continuously. This prevents Intellisense from updating itself, since it is waiting for a small pause in my typing which never comes. How small? This is where the comboboxes of the navigation bar come in handy.<br /><br />Try this: add a method to one of your classes in your header file, say void my_class::show_something(); Now switch to the end of the corresponding cpp file and type void my_class::show_something() { sometext.... . Do so without a pause. Watch the right combobox. Initially it is empty. About one second after you stopped typing it should change to show_something. This indicates that Intellisense finally caught up and will work inside show_something again. But if you continue typing without a pause, the combo box stays empty or takes a lot longer to fill. This gives the impression that Intellisense does not work, especially when I need it most: when I write new code.<br /><br /><span style="font-weight: bold;">After you've changed or added a definition, stop typing </span><span style="font-weight: bold;"> while inside a method </span><span style="font-weight: bold;">and wait until the upper right combobox shows the name of the method again.<br /><br />Use the upper right combobox as an indicator for Intellisense.<br /></span><br />Also I find that Intellisense is extremely sensible to temporary code changes. Consider this:<br /><br /><blockquote style="font-family: courier new;">void my_method()<br />{<br /> if (something) {<br /> //<><br /><br />This happens all the time while you are changing code. When you introduce a new compound statement and type the opening bracket {, for a short time the code is invalid. The closing bracket is missing. This sort of code changes can confuse Intellisense. It has difficulties parsing the code. Since it is rather slow on updating it may take another second after you've added the closing brace } until it is working again.<br /><br />If you want to know more about this, open the task manager and have an eye on the CPU usage. Experiment a little, get to know Intellisense, adapt your speed and see Intellisense improve.<br /><br />So usually, when hitting Ctrl+Space does not expand the name rightaway, I pause of for a second and try again.<br /><br /><span style="font-weight: bold;">Note: It is important to close the Intellisense member list combobox if it is open. Intellisense does not update itself while it is open.</span><br /><br />Something else I found: Editing a source file in another editor such as DialogBlocks also seems to confuse Intellisense. I haven't yet found out why. Sometimes the entire class definition stops working, somethings it seems unaffected. I'll keep you updated if I find something. If you've got an idea, please leave a comment.Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1136201923473725622006-01-02T12:24:00.000+01:002006-01-02T12:41:58.433+01:00wxWidgets, VC++8.0 and XP theme look and feelI an previous post I talked about setting wxUSE_NO_MANIFEST=0 to avoid linker errors when you compile your wxWidgets application with Visual Studio 8.0. Turns out this was only half the story.<br /><span style="font-weight: bold;"><br />Symptoms:</span> Your application looses Windows XP themes for all controls.<br /><span style="font-weight: bold;"><br />Solution:</span> Add these lines to your precompiled header file "stdwx.h" if you have one, or to one of your .cpp files, preferably the applications .cpp file. (Please unwrap these lines. Every line should start with the preprocessor symbol '#'):<br /><blockquote><span style="font-size:78%;">#if _MSC_VER>=1400<br />#if defined _M_IX86<br />#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")<br />#elif defined _M_IA64<br />#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")<br />#elif defined _M_X64<br />#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")<br />#else<br />#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")<br />#endif<br />#endif</span><br /></blockquote><span style="font-weight: bold;">Background:</span> With Windows XP, your application must include a 'manifest' in its binary. This manifest tells Windows XP what version of what DLL your application uses. Among other things this enables 'Side By Side' installation, where two applications load a DLL of the same name, but with different versions.<br />Beginning with Visual Studio 8.0, the CRT runtime library DLLs are no longer found in the $PATH environment variable. Instead the linker generates a manifest for each application created with VS 8 and writes the requested DLL version into the binary. You must define wxUSE_NO_MANIFEST=0 for the .rc file to tell the resource compiler to ignore the standard wx manifest file. Otherwise the linker will give an error, since it then would see two manifests, the wx manifest and the one it generates itself.<br />But the manifest file generated by the linker does not include the neccessary information for the common controls version 6.0, which is responsible for the Windows XP themes for controls such as buttons. So by default, the application will have the XP themed frame with the blue bar and the bright red 'X' button in the upper right corner, but will still have the old Win2K style buttons inside the frame.<br />The reference to the common controls version 6.0 was contained in the wx manifest, which is no longer used due to wxUSE_NO_MANIFEST.<br />The lines above tell the linker to add the common controls version 6.0 to the manifest and your application will get the XP theme look back for its controls.Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com1tag:blogger.com,1999:blog-14343329.post-1135857602943457902005-12-29T13:00:00.000+01:002005-12-29T13:09:37.466+01:00wxWidgets resources compile again every time you hit "Run" (F5)Another wxWidgets related answer...<br /><br />Symptoms:<br />You include the wxWidgets resources in your project (wxrc.rc). Now everytime you hit 'Run' (F5) Visual Studio (7) thinks it has to rebuild some of the sources and the resources.<br /><br />Solution:<br />Create a file 'inc_wxrc.rc' with the following contents:<br /><blockquote>//{{NO_DEPENDENCIES}}<br />// Microsoft Visual C++ generated include file.<br />// Used by mikadoApp.rc<br />//<br />#include <wx><br /></wx></blockquote><blockquote></blockquote>Do not add wx.rc to your project directly. Either add 'inc_wxrc.rc' to the project or, if you have a .rc file that includes wx.rc, include 'inc_wxrc.rc' instead.<br /><br />Advertisment (for my own product):<br />The <a href="http://www.litwindow.com/wxvisualsetup?gad=CMPInrUBEghPz_5vGK_ioBjO6qv_AyDDycwK&gkw=resources">wxVisualSetup Project Wizard</a> does this automatically and even add a version resource to your binary. It is available for €39.95 for Visual Studio 2002, 2003 and 2005, all versions.<br /><br />Best regards<br /><br />HajoHajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1135690370523010752005-12-27T14:19:00.000+01:002005-12-27T14:39:54.303+01:00Compile error using XRC and Unicode (VC++ 8.0 Express)Since I've seen this asked several times now, I'll blog the answer:<br /><br />You are using wxWidgets XRC Resources and compile a Unicode application (which is default on Visual Studio 8.0 Express) and are getting an error similar to<br /><span class="postbody"><span style="font-style: italic;"></span><blockquote><span style="font-style: italic;">xrcdemo.cpp:63:1: pasting "LL" and "L"UsernameField"" does not give a valid preprocessing token </span><br /><span style="font-style: italic;"> xrcdemo.cpp: In member function `void LoginDialog::InitWidgetsFromXRC()': </span></blockquote><span style="font-style: italic;"></span>The error is in how you are using the XRC macros. <span style="font-weight: bold;">XRC macros don't accept string constants enclosed in _T(). Use string contants without _T(), </span>even in Unicode builds.<br /><br /></span> <div style="text-align: center;"> <div style="text-align: right;"><span class="postbody"><span style="font-size:130%;"><span style="font-weight: bold;">Summary: </span>Use _T() with XRC methods, do not use _T() with XRC macros.</span></span><br /></div> <span class="postbody"><br /></span></div> <span class="postbody"><br />Suppose you have<br /><br /></span><span style="font-style: italic;"> </span><a style="font-style: italic;" href="http://www.wxwidgets.org/manuals/2.6.2/wx_wxxmlresource.html#wxxmlresource"><span class="kw3"></span></a><blockquote><a style="font-style: italic;" href="http://www.wxwidgets.org/manuals/2.6.2/wx_wxxmlresource.html#wxxmlresource"><span class="kw3">wxXmlResource</span></a><span style="font-style: italic;">::Get</span><span style="font-style: italic;" class="br0">()</span><span style="font-style: italic;">->LoadDialog</span><span style="font-style: italic;" class="br0">(</span><span style="font-style: italic;" class="kw2">this</span><span style="font-style: italic;">, </span><span style="font-style: italic;" class="kw2">NULL</span><span style="font-style: italic;">, _T</span><span style="font-style: italic;" class="br0">(</span><span style="font-style: italic;" class="st0">"LoginDialog"</span><span style="font-style: italic;" class="br0">))</span><span style="font-style: italic;">; </span><br /><span style="font-style: italic;"> UsernameField = XRCCTRL</span><span style="font-style: italic;" class="br0">(</span><span style="font-style: italic;">*</span><span style="font-style: italic;" class="kw2">this</span><span style="font-style: italic;">, _T</span><span style="font-style: italic;" class="br0">(</span><span style="font-style: italic;" class="st0">"UsernameField"</span><span style="font-style: italic;" class="br0">)</span><span style="font-style: italic;">, </span><a style="font-style: italic;" href="http://www.wxwidgets.org/manuals/2.6.2/wx_wxtextctrl.html#wxtextctrl"><span class="kw3">wxTextCtrl</span></a><span style="font-style: italic;" class="br0">)</span><span style="font-style: italic;">; </span></blockquote><span style="font-style: italic;"></span><br /><span style="font-style: italic;" class="co1"></span><br />You must enclose strings in _T(), if you want to be able to compile your application in both, ANSI and Unicode builds. So <span style="font-style: italic;">LoadDialog(this, NULL, _T("LoginDialog"));</span> is the correct way to specify 'LoginDialog' as the XRC resource.<br /><br /><span style="font-weight: bold;">But,</span> the XRC macros that take a string use _T() internally already. So <span style="font-style: italic;">XRCCTRL(*this, _T("UsernameField"), wxTextCtrl)</span> is wrong, because XRCCTRL is a macro and it adds _T() to the 'UsernameField' parameter itself. The compiler then sees something like<br /><br /><span style="font-style: italic;"><blockquote>xrcctrl_function(*this, _T( _T("UsernameField") ), wxTextCtrl)</blockquote><span style="font-style: italic;"><span style="font-style: italic;"><span style="font-style: italic;"><br /><br /></span></span></span></span>and prints an error.<br /><br /><span style="color: rgb(51, 51, 255);">The correct use of the XRCCTRL and similar macros is to pass in strings <span style="font-weight: bold;">without</span> _T(), even in Unicode builds.<br /><br /></span><span style="font-style: italic;"> </span><blockquote><span style="font-style: italic;">UsernameField = XRCCTRL</span><span style="font-style: italic;" class="br0">(</span><span style="font-style: italic;">*</span><span style="font-style: italic;" class="kw2">this</span><span style="font-style: italic;">, </span><span style="font-style: italic;" class="br0"></span><span style="font-style: italic;" class="st0">"UsernameField"</span><span style="font-style: italic;" class="br0">)</span><span style="font-style: italic;">, </span><a style="font-style: italic;" href="http://www.wxwidgets.org/manuals/2.6.2/wx_wxtextctrl.html#wxtextctrl"><span class="kw3">wxTextCtrl</span></a><span style="font-style: italic;" class="br0">)</span><span style="font-style: italic;">;</span></blockquote><span style="font-style: italic;"><br /></span>Some more background for the interested: _T() is a simple macro that simply adds the literal 'L' in front of a quoted string in Unicode builds and does nothing in ANSI builds. Unicode strings are wide-character strings (wchar_t). Wide-character string constants must be preceded by an L in C++.<br /><br /><blockquote> "This is a char string (1byte), or ANSI string"<br />L"This is a wide-char string (2byte/4byte under linux), or Unicode string"</blockquote><br /><br />Using <span style="font-style: italic;">XRCCTRL(*this, _T("UsernameField"), wxTextCtrl)</span> would expand to <span style="font-style: italic;">xrcctrl_function(*this, LL"UsernameField", wxTextCtrl)</span>, which is what the compiler error message says.<br /><br />Best regards<br /><br />Hajo Kirchhoff<br /><br />P.S. Please have a look at <a href="http://www.litwindow.com/wxvisualsetup">wxVisualSetup for Visual Studio 2002/2003 or 2005</a> to support this blog.<br /><span style="font-style: italic;"><span style="font-style: italic;"><span style="font-style: italic;"><span style="font-style: italic;"></span></span></span></span>Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1135358370009625522005-12-23T18:08:00.000+01:002005-12-23T18:19:30.016+01:00wxVisualSetup for Visual Studio 2005 releasedI have just released the next version of wxVisualSetup, the package that integrates the wxWidgets GUI class library into Microsofts Visual Studio IDEs. If you are using wxWidgets and Microsoft Visual Studio, you can find more information at <a href="http://www.litwindow.com/wxvisualsetup?gad=CLfl9rQBEgiiNu7R-cqkbhjO6qv_AyCDvsUK">http://www.litwindow.com/wxvisualsetup</a>Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1135265544032898922005-12-22T16:28:00.000+01:002005-12-22T16:32:24.043+01:00Your application crashes (fails to initialise) with Visual Studio 8.0 and wxWidgets DLLsIf you are using Visual Studio 8.0 and wxWidgets DLLs and your application crashes immediately after starting or cannot be initialised properly, here is the fix:<br /><br /><ol> <li>Open the file $(WXWIN)\src\msw\main.cpp</li> <li>Search for the function 'DllMain'</li> <li>Remove the entire function</li> <li>Recompile wxWidgets DLLs</li> </ol> Background:<br /><br />When you open the wxWidgets .dsp files with Visual Studio 8.0, the solution conversion wizard adds the preprocessor symbol _WINDLL to the solution. This symbol is not defined when you compile the DLLs with older Visual Studios (.NET or 2003). When it is defined, the function DllMain gets compiled and included. This is causing the crash.Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com4tag:blogger.com,1999:blog-14343329.post-1134987125677868432005-12-19T10:31:00.001+01:002005-12-19T15:30:39.643+01:00Upgrading your wxWidgets project to Visual Studio 8.0<div style="text-align: left;"><span style="font-size:100%;">Ok, so I finally found some time to start using my brand-new MS Visual Studio 2005 and try and compile my projects with it. As usual, moving to the next major version of a Microsoft Compiler involves a lot of work. Here is what I found so far:</span><br /></div> <span style="font-size:100%;"><br /></span><span style="font-size:100%;"><span style="font-size:130%;">Warnings, warnings, warnings...</span><br />The first thing you'll notice are hundreds of warnings. In an effort to make software more secure, MS has depreciated many runtime library functions that where the cause of so many buffer overflows. No more strcpy, strdup, localtime etc... Same with std::copy and other functions that are being used by the C++ standard runtime library.<br />I am not sure I really like this. This seems like a good idea at first, but only for new projects. For all other projects, people are probably simply going to disable the warnings. That is what I recommend when you upgrade your projects. Especially your wxWidgets projects, since the recommended replacement functions strcpy_s, localtime_c etc... are MS-specific and not found in any other compiler I know of.<br /><br />So, in your project, define these macros<br /><br />#define _CRT_SECURE_NO_DEPRECATE<br />tell the compiler to stop complaining about using localtime etc...<br /><br />#define _SCL_SECURE_NO_DEPRECATE<br />stop complaining about std::copy etc...<br /><br />The best place to do this is in your precompiled header file such as stdwx.h, if you have one. Of course you can add these defines to the project file itself, but you'll have to add them to all configurations.<br /><br /></span><span style="font-size:100%;"><span style="font-size:130%;">POSIX vs. ISO</span><br />More warnings: Microsoft also depreciated all Posix names. Did you know strcpy should actually be called _strcpy in the ISO standard? I know now. All Posix names - and there are many - have been depreciated in favor of the new ISO standard. Good idea? Probably. Someone has to make a start. But unless you are willing to spend time changing your code </span><span style="font-weight: bold;font-size:100%;" >and</span><span style="font-size:100%;"> your gnu or whatever other compiler you are using supports it, you'll want to disable these warnings as well:<br /><br />#define _CRT_NONSTDC_NO_DEPRECATE<br />to stop complaining about old POSIX names<br /></span><span style="font-size:100%;"><br /><span style="font-size:130%;">Manifest</span><br />If you include the wx.rc file - which you should -, you will get an error when you compile your project with VC 8.0: invalid COFF or something. The new 8.0 linker has the ability to generate a manifest file on the fly and the .dsp/.sln conversion wizard enables this by default. So now your application has two manifest files, one included via wx.rc and the new one generated by the linker. The linker does not like this. You have two options.<br /></span> <ol> <li><span style="font-size:100%;">Define wxUSE_NO_MANIFEST to stop using the wxWidgets manifest file. You must change the file settings for your .rc file and include this define there. Using the precompiled header file as mentioned above won't work.</span></li> <li><span style="font-size:100%;">Disable the linker|manifest option: Open the properties for your project. Select 'all configurations', open the linker settings, manifest file and set 'generate manifest' to no.</span></li> </ol><span style="font-size:100%;">I recommend the first option as it lets the linker create a manifest with all the correct DLL side-by-side information. If you disable the linker|manifest option, a debug build may not find the MSVCP80D.DLL for example and you have to copy the DLL into your path. If the linker creates the manifest file, all the correct DLL paths are being used.<br /></span><span style="font-size:100%;"><br /></span>Hajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1120934832211742482005-07-09T20:39:00.000+02:002005-07-19T12:43:15.900+02:00Intellisense stops working for a class...Symptoms: Intellisense stops working for an entire class.<br />Conditions:<br /><ul> <li>You are exporting symbols with a preprocessor macro #defined to _declspec(dllexport or dllimport)</li> <li>You are exporting individual members of a class as opposed to the entire class<br /></li> </ul> I have a love/hate relationship with my Visual Studio Intellisense. It is a great feature ... if it works. I'm working on an C++ ODBC wrapper class right now and Intellisense did not like my odbc::statement class. I am using an LWODBC_API define that expands to _declspec(dllimport/dllexport) to import or export my classes and members. But I did not want to export the entire odbc::statement class. The class contains a couple of map<> and other constructs that cannot be accessed by 'clients of the DLL'. You know the error.<br /><br /><pre>class LWODBC_API statement {<br />public:<br />statement();<br />~statement();<br />protected:<br />my_map m_member;<br />};</pre><br />will give a 'Warning: m_member needs DLL-interface to be accessed by clients of...'.<br /><br />To circumvent that I decided to export the statement members individually.<br /><br /><pre>class statement {<br />public:<br />LWODBC_API statement();<br />LWODBC_API ~statement();<br />protected:<br />my_map m_member;<br />};</pre>Now 'my_map' is no longer exported and the warning is gone.<br /><br />But this confuses Intellisense and the entire class 'statement' will not work anymore, nor will any class after 'statement'. You might not even notice this rightaway, since Intellisense will use the 'statement' information it has in it's *.ncb database. But any new members you add will not show up.<br /><br />It took me a while of deleting declarations in the header file, closing the project, deleting the *.ncb database, reopening the project and trying Intellisense until I found out that<br /><br />LWODBC_API ~statement();<br /><br />was the culprit. The Intellisense parser fails on this, even when it properly resolves LWODBC_API to _declspec(dllexport).<br /><br />The workaround is simple:<br /><br />Before the class declaration, preferably at the top of the header file, add<br /><br /><pre>#ifdef INTELLISENSE_STOPS_WORKING_WORKAROUND<br />#undef LWODBC_API<br />#define LWODBC_API<br />#endif</pre><br /><br />This makes Intellisense believe that LWODBC_API is actually defined empty and Intellisense will now see<br /> ~statement();<br /><br />and be happy.<br /><br />But there is one catch. It would be nice if one could add the workaround right after the LWODBC_API definition and have it done once and for all headers. But this does not work if the LWODBC_API definition is contained in a different header file.<br /><br /><div style="text-align: center;">The workaround must be in the same header file where Intellisense stumbles.<br /></div><br />If you have multiple header files that Intellisense fails to parse correctly, you must include the workaround in each file. And you must include the workaround after the #include statement that includes the LWODBC_API definition.<br /><br />I'm back to work now and much relieved that I have my Intellisense back for my class.<br /><br />HajoHajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0tag:blogger.com,1999:blog-14343329.post-1120934363935417052005-07-09T20:34:00.000+02:002005-07-09T21:33:45.636+02:00Welcome to my logbookHi all,<br />I just found out why Visual Studio's Intellisense stopped working for one of my classes and decided to share this information.<br /><br />I am software developer working with Visual Studio .NET 2003 under Windows using wxWidgets and boost and a lot of other stuff. In the past I frequently hunted down obscure bugs or found workarounds for bugging problems and wrote about them on my main page. If you want to know for example how to create a (reasonably) secure MS Access database via ODBC, you will find the answer at <a href="http://www.litwindow.com/Knowhow/knowhow.html">http://www.litwindow.com/Knowhow/knowhow.html</a>.<br /><br />But changing my <a href="http://www.litwindow.com">webpage</a> everytime I find a little software programming gem or a workaround is too much of a hassle. So I decided to start a blog where I will be sharing what I find. I hope this will be useful to someone. If it is, feel free to spread the word and add your comments.<br /><br />Best regards<br /><br />Hajo KirchhoffHajo Kirchhoff of Lit Window Productionshttp://www.blogger.com/profile/11225289575673500696noreply@blogger.com0