Wednesday, August 24, 2011

Replacing the dual list box dialog

I wrote about nine months ago about how I improved the dual list box dialog which came as a standard template with Delphi 1. I used this dialog once again in a program in which I was initially displaying one field from a database; after testing, I decided to add another field in order to make things clearer. When the OP saw it, she requested another field; afterwards I decided on my own to add yet another field. As a result, the list box was displaying four fields concatenated into one string and the result was visually unpleasant. I used different kinds of brackets for each field, so the string looked something like this
5298 (1110) [31-07-2011] {2543}
The operation of the dialog was oblivious to the content of the string, as the key (deliberate pun) to the string was passed as a linked and hidden object, but from the user's point of view, the modest list box was not up to the task of displaying such a complex line. It was time for a more structured component to hold the string/fields.

Normally, when displaying data taken from a database, my thoughts turn to using a dbgrid, but as the dialog required rows to be removed from one list and added to another, the use of queries would needlessly complicate matters. A compromise solution would be to use a listview. So yesterday, I wrote a small demo program with two listviews; I populated one on program startup and then figured out how to move one item from listview to listview.

I thought that the demo would be sufficient for plugging directly into the program needing this functionality, but I was wrong. First of all, I had to contend with the right to left issue; fortunately I had solved this in the past, so all I had to do was find where I had implemented this. Then I had to figure out the column widths, which was done by excruciating trial and error (there must be a better way!). Then I was left with the task of iterating through one of the listviews and checking whether the current entry had been in the populating query upon startup or had been added during the form's execution; this was the most important problem of all but eventually succumbed.

Whilst none of the above seems to be on the cutting edge of Delphi development, I have found it very difficult to find any references to the sort of problem which I face. So I have to 'wing it' most of the time, creating my own solutions to problems which I encounter. But now that the 'dual list view dialog' problem has been solved, I can reuse this solution whenever needed. 

Here is a little of code, which deals with moving one item or all the items from one list view to the other.
Procedure TConnectToReceipt.LVMoveOne (src, dst: tlistview);
var
 li1, li2: tlistitem;

begin
 if src.selected <> nil then
  begin
   li1:= src.Selected;
   li2:= dst.Items.Add;
   li2.Assign (li1);
   src.selected.delete;
  end;
 dst.alphasort;
 setbuttons;
end;

Procedure TConnectToReceipt.LVMoveAll (src, dst: tlistview);
var
 i: Integer;

begin
 src.Items.BeginUpdate;
 dst.Items.BeginUpdate;
 for i:= src.Items.Count - 1 downto 0 do
  begin
   dst.Items.Add.Assign (src.Items[i]);
   src.Items[i].Delete;
  end;
 dst.alphasort;
 dst.Items.EndUpdate;
 src.Items.EndUpdate;
 setbuttons;
end;
And here's the code to make the listviews (and their titles) display from right to left. I'm posting this here to make it easier for me to find it the next time.
procedure TConnectToReceipt.FormCreate(Sender: TObject);
const
 LVM_FIRST = $1000;      // ListView messages
 LVM_GETHEADER = LVM_FIRST + 31;

var
 header: thandle;

begin
 header:= SendMessage (srclist.Handle, LVM_GETHEADER, 0, 0);
 SetWindowLong  (header,   GWL_EXSTYLE,
GetWindowLong (header,  GWL_EXSTYLE) or  WS_EX_LAYOUTRTL or  WS_EX_NOINHERITLAYOUT);
 SetWindowLong (srclist.Handle,GWL_EXSTYLE,
                GetWindowLong (srclist.Handle, GWL_EXSTYLE)  or
                WS_EX_LAYOUTRTL or WS_EX_NOINHERITLAYOUT);
 srclist.invalidate;
end;

No comments: