<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<META NAME="Generator" CONTENT="MS Exchange Server version 6.5.7638.1">
<TITLE>DataGridView in virtual mode sample.</TITLE>
</HEAD>
<BODY>
<!-- Converted from text/plain format -->
<P><FONT SIZE=2>Hi all,<BR>
<BR>
I have implemented a little example that shows the use of the .net DataGridView control in virtual mode using RDNZL and Lispworks.<BR>
<BR>
The DataGridView control is particularly difficult to get working properly. I can't count the number of times I crashed my environment getting this working but foreign function calls are always hairy.<BR>
<BR>
The only thing you should have to do to get it working is change the location of the RDNZL path.<BR>
<BR>
Enjoy,<BR>
<BR>
Matthew O'Connor<BR>
<BR>
;; ========================================================================<BR>
;; DataGridView Experiment.<BR>
;;<BR>
;; Matthew O'Connor<BR>
;;<BR>
;; This is basically a lisp implementation of the Microsoft "Walkthrough:<BR>
;; Implementing Virtual Mode in the Windows Forms DataGridView Control".<BR>
;;<BR>
;; The DataGridView is running in Virtual mode continually coming back<BR>
;; to Lisp through the callbacks to get and set data.<BR>
;;<BR>
;; The example is made a little more complex by the use of an object to<BR>
;; store the row that is currently being edited. This allows a cell edit<BR>
;; to be undone with a simple press of the Escape key. A second press<BR>
;; of the same key will undo the changes made to the entire row.<BR>
;;<BR>
;; As usual there is no warranty. Use at your own peril. I am relatively<BR>
;; new to Lisp so you are warned.<BR>
<BR>
;; NOTE: You need to modify this to point to the location of RDNZL on your system.<BR>
(load "../RDNZL/rdnzl-0.12.0/load.lisp")<BR>
<BR>
;; ------------------------------------------------------------------------<BR>
;; RDNZL setup.<BR>
<BR>
(rdnzl:enable-rdnzl-syntax)<BR>
<BR>
(rdnzl:import-types "System.Windows.Forms"<BR>
"Application" "Form" "DockStyle" "DataGridView"<BR>
"DataGridViewTextBoxColumn" "DataGridViewCellValueEventHandler"<BR>
"DataGridViewRowEventHandler" "QuestionEventHandler"<BR>
"DataGridViewCellEventHandler" "DataGridViewRowCancelEventHandler")<BR>
(rdnzl:use-namespace "System.Windows.Forms")<BR>
<BR>
;; ------------------------------------------------------------------------<BR>
;; Utilities<BR>
<BR>
(defun filter (lst fn)<BR>
(let ((acc nil))<BR>
(dolist (x lst)<BR>
(let ((val (funcall fn x)))<BR>
(if val (push x acc))))<BR>
(nreverse acc)))<BR>
<BR>
;; ------------------------------------------------------------------------<BR>
;; Units<BR>
<BR>
;; The items that will be stored in the database. At the moment they are<BR>
;; simply all strings.<BR>
(defclass unit ()<BR>
((code :accessor code :initarg :code :initform "None")<BR>
(description :accessor description :initarg :description :initform "None")<BR>
(name :accessor name :initarg :name :initform "Unknown")))<BR>
<BR>
;; Create a unit with an optional description.<BR>
(defun make-unit (code name &optional description)<BR>
(if description<BR>
(make-instance 'unit :code code :name name :description description)<BR>
(make-instance 'unit :code code :name name)))<BR>
<BR>
;; Clone a unit this is needed by the datagridview which makes an copy whilst<BR>
;; editing a row.<BR>
(defun clone-unit (unit)<BR>
(make-unit (code unit) (name unit) (description unit)))<BR>
<BR>
;; ------------------------------------------------------------------------<BR>
;; units database<BR>
<BR>
;; A simple database of units.<BR>
(defparameter *database-units* nil)<BR>
<BR>
(defun database-add-unit (unit)<BR>
(setf *database-units* (append *database-units* (list unit))))<BR>
<BR>
(defun database-remove-unit (unit)<BR>
(setf *database-units* (remove unit *database-units*)))<BR>
<BR>
(defun database-remove-unit-at (index)<BR>
(setf *database-units* (remove (nth index *database-units*) *database-units*)))<BR>
<BR>
(defun database-get-unit (code)<BR>
(first (filter *database-units* #'(lambda (unit) (equal code (code unit))))))<BR>
<BR>
(defun database-get-unit-at (index)<BR>
(nth index *database-units*))<BR>
<BR>
(defun database-update-unit-at (index unit)<BR>
(setf (nth index *database-units*) unit))<BR>
<BR>
(defun database-units-count ()<BR>
(length *database-units*))<BR>
<BR>
;; The data grid view that is used to represent the units.<BR>
(defparameter *units-data-grid-view* nil)<BR>
<BR>
;; Keeps track of the row index that we are currently editing. A value<BR>
;; of -1 indicates that we are not editing anything at the moment.<BR>
(defparameter *row-in-edit* -1)<BR>
<BR>
;; This is the unit that will be created to hold edits until they have<BR>
;; been validated. At this point they will be written back to the database.<BR>
(defparameter *unit-in-edit* nil)<BR>
<BR>
;; I'm not sure about this variable. It never seems to be altered anywhere<BR>
;; in the example code.<BR>
(defparameter *row-scope-commit* t)<BR>
<BR>
;; A little utility to get the count of rows.<BR>
(defun data-grid-view-row-count ()<BR>
[%Count [%Rows *units-data-grid-view*]])<BR>
<BR>
;; Are we editing this row already.<BR>
(defun is-row-in-edit (index)<BR>
(= *row-in-edit* index))<BR>
<BR>
(defun is-new-row (index)<BR>
(= index (- (data-grid-view-row-count) 1)))<BR>
<BR>
;; Is the supplied index the new row.<BR>
(defun is-new-row-from-row (row)<BR>
[%IsNewRow row])<BR>
<BR>
<BR>
;; We are no longer editing any row so set the variables back to their<BR>
;; defaults.<BR>
(defun reset-unit-in-edit ()<BR>
(setf *unit-in-edit* nil<BR>
*row-in-edit* -1))<BR>
<BR>
;; ------------------------------------------------------------------------<BR>
;; Implement the DataGridView callbacks.<BR>
;;<BR>
;; These are reasonably complex little calls. I refer you to the "Walkthrough"<BR>
;; mentioned at the start of the file for further information on how they<BR>
;; work.<BR>
<BR>
;; The event occurs whenever the DataGridView requires the value to a cell for<BR>
;; display. It retrieves the value from either the database of units or from the<BR>
;; unit currently being edited.<BR>
;;<BR>
;; DataGridViewCellValueEventArgs<BR>
;; int ColumnIndex - The index of the column.<BR>
;; int RowIndex - The index of the row.<BR>
;; object Value - The value of the row.<BR>
(defun cell-value-needed (object event)<BR>
(let ((row-index [%RowIndex event])<BR>
(column-index [%ColumnIndex event])<BR>
(unit nil))<BR>
(unless (is-new-row row-index)<BR>
(if (is-row-in-edit row-index)<BR>
;; then<BR>
(setf unit *unit-in-edit*)<BR>
;; else<BR>
(setf unit (database-get-unit-at row-index)))<BR>
(unless (equal unit nil)<BR>
(case column-index<BR>
((0) (setf [%Value event] (code unit)))<BR>
((1) (setf [%Value event] (name unit)))<BR>
((2) (setf [%Value event] (description unit))))))))<BR>
<BR>
;; This event occurs whenever a cell value has been updated on the DataGridView.<BR>
;; It gives the underlying storage mechanism a chance to update the value.<BR>
;;<BR>
;; If there is no unit currently in edit one is created. The updates do not go<BR>
;; directly to the database. They go via the unit currently in edit first. Once<BR>
;; the changes have been validated they are updated in the database.<BR>
;;<BR>
;; DataGridViewCellValueEventArgs<BR>
;; int ColumnIndex - The index of the column.<BR>
;; int RowIndex - The index of the row.<BR>
;; object Value - The value of the row.<BR>
(defun cell-value-pushed (object event)<BR>
(let ((row-index [%RowIndex event])<BR>
(column-index [%ColumnIndex event])<BR>
(unit-tmp nil))<BR>
(if (< row-index (length *database-units*))<BR>
(progn<BR>
(when (equal *unit-in-edit* nil)<BR>
(setf *unit-in-edit* (clone-unit (database-get-unit-at row-index))))<BR>
(setf unit-tmp *unit-in-edit*<BR>
*row-in-edit* row-index))<BR>
;; else<BR>
(setf unit-tmp *unit-in-edit*))<BR>
(unless (equal unit-tmp nil)<BR>
(let ((value [%Value event]))<BR>
(case column-index<BR>
((0) (setf (code unit-tmp) (rdnzl:unbox (rdnzl:cast value "System.String"))))<BR>
((1) (setf (name unit-tmp) (rdnzl:unbox (rdnzl:cast value "System.String"))))<BR>
((2) (setf (description unit-tmp) (rdnzl:unbox (rdnzl:cast value "System.String")))))))))<BR>
<BR>
;; Occurs whenever a new row is needed at the end of a DataGridView. It simply<BR>
;; creates a new empty unit that becomes the current unit in edit.<BR>
;;<BR>
;; DataGridViewRowEventArgs:<BR>
;; DataGridViewRow Row - I'm not sure what the contents of the row are<BR>
;; as I currently don't use them.<BR>
(defun new-row-needed (object event)<BR>
(setf *unit-in-edit* (make-unit "" "" "")<BR>
*row-in-edit* (- (data-grid-view-row-count) 1)))<BR>
<BR>
;; This event occurs after a row has finished validating. At this point the data<BR>
;; can be pushed to the database.<BR>
;;<BR>
;; DataGridViewViewCellEventArgs<BR>
;; int ColumnIndex - The index of the column.<BR>
;; int RowIndex - The index of the row.<BR>
(defun row-validated (object event)<BR>
(let ((row-index [%RowIndex event]))<BR>
(if (and (>= row-index (database-units-count))<BR>
(not (= row-index (- (data-grid-view-row-count) 1) )))<BR>
(progn<BR>
(database-add-unit *unit-in-edit*)<BR>
(reset-unit-in-edit))<BR>
(if (and (not (equal *unit-in-edit* nil))<BR>
(< row-index (database-units-count)))<BR>
(progn<BR>
(database-update-unit-at row-index *unit-in-edit*)<BR>
(reset-unit-in-edit))<BR>
(if [%ContainsFocus *units-data-grid-view*]<BR>
(reset-unit-in-edit))))))<BR>
<BR>
;; Used by the DataGridView to determine if current row has uncommitted changes.<BR>
;;<BR>
;; Note - I'm not quite sure how this works.<BR>
;;<BR>
;; QuestionEventArgs<BR>
;; Boolean Response - True or False.<BR>
(defun row-dirty-state-needed (object event)<BR>
(unless *row-scope-commit*<BR>
(setf [%Response event] [%IsCurrentCellDirty *units-data-grid-view*])))<BR>
<BR>
;; Give the application the opportunity to cancel the edits in a row.<BR>
;;<BR>
;; A single escape key cancels the cell edit. A double cancels the row edit.<BR>
;;<BR>
;; QuestionEventArgs<BR>
;; Boolean Response - True to cancel the row-edit, False (I assume) to let it<BR>
;; continue.<BR>
(defun cancel-row-edit (object event)<BR>
(if (and (= *row-in-edit* (- (data-grid-view-row-count) 2))<BR>
(= *row-in-edit* (database-units-count)))<BR>
(setf *unit-in-edit* (make-unit "" "" ""))<BR>
(reset-unit-in-edit)))<BR>
<BR>
;; Remove the row if it is in the database otherwise simply reset the unit in edit.<BR>
;;<BR>
;; DataGridViewRowCancelEventArgs:<BR>
;; DataGridViewRow Row - The row the user is deleting.<BR>
;; Boolean Cancel - Cancel the addition of the row.<BR>
(defun user-deleting-row (object event)<BR>
(let* ((data-grid-row [%Row event])<BR>
(row-index [%Index data-grid-row]))<BR>
(when (< row-index (database-units-count))<BR>
(database-remove-unit-at row-index))<BR>
(when (= row-index *row-in-edit*)<BR>
(reset-unit-in-edit))))<BR>
<BR>
<BR>
;; ------------------------------------------------------------------------<BR>
;; Main<BR>
<BR>
;; Create a new text box column.<BR>
(defun create-column (index name heading)<BR>
(let ((col (rdnzl:new "DataGridViewTextBoxColumn")))<BR>
(setf [%DisplayIndex col] index<BR>
[%Name col] name<BR>
[%HeaderText col] heading)<BR>
col))<BR>
<BR>
;; Create a DataGridView with three columns; Code, Name and Description.<BR>
;; It is placed in Virtual mode and all of the callbacks are hooked up.<BR>
(defun create-units-editor ()<BR>
(setf *units-data-grid-view* (rdnzl:new "DataGridView")<BR>
*row-in-edit* -1<BR>
*unit-in-edit* nil<BR>
*row-scope-commit* t<BR>
[%VirtualMode *units-data-grid-view*] t <BR>
[%Dock *units-data-grid-view*] [$DockStyle.Fill]<BR>
[%Text *units-data-grid-view*] "DataGridView Experiment"<BR>
[%RowHeadersVisible *units-data-grid-view*] t)<BR>
[Add [%Columns *units-data-grid-view*] (create-column 0 "code" "Code")]<BR>
[Add [%Columns *units-data-grid-view*] (create-column 1 "name" "Name")]<BR>
[Add [%Columns *units-data-grid-view*] (create-column 2 "description" "Description")]<BR>
<BR>
[+CellValueNeeded *units-data-grid-view* (rdnzl:new "DataGridViewCellValueEventHandler" #'cell-value-needed)]<BR>
[+CellValuePushed *units-data-grid-view* (rdnzl:new "DataGridViewCellValueEventHandler" #'cell-value-pushed)]<BR>
[+NewRowNeeded *units-data-grid-view* (rdnzl:new "DataGridViewRowEventHandler" #'new-row-needed)]<BR>
[+UserDeletingRow *units-data-grid-view* (rdnzl:new "DataGridViewRowCancelEventHandler" #'user-deleting-row)]<BR>
[+CancelRowEdit *units-data-grid-view* (rdnzl:new "QuestionEventHandler" #'cancel-row-edit)]<BR>
[+RowDirtyStateNeeded *units-data-grid-view* (rdnzl:new "QuestionEventHandler" #'row-dirty-state-needed)]<BR>
[+RowValidated *units-data-grid-view* (rdnzl:new "DataGridViewCellEventHandler" #'row-validated)])<BR>
<BR>
;; Call this function to run the application.<BR>
(defun run-datagridview-experiment ()<BR>
(let ((form (rdnzl:new "Form")))<BR>
(create-units-editor)<BR>
(setf [%Text form] "DataGridView Experiment")<BR>
[Add [%Controls form] *units-data-grid-view*]<BR>
[BringToFront *units-data-grid-view*]<BR>
[Application.Run form]))<BR>
<BR>
(run-datagridview-experiment)<BR>
<BR>
<BR>
<BR>
</FONT>
</P>
</BODY>
</HTML>