[MDD]: I've made a pure-Tcl client/server DP-RPC-compatible version of my [A Mini Database Manager]. It uses the dp_RPC facility (http://www.eolas.net/tcl/spynergy/rpc/dp_RPC.tcl) that was part of [Eolas]' old Spynergy Toolkit. The documentation for dp_RPC.tcl can be seen at http://www.eolas.net/tcl/spynergy/rpc/ BTW: You'll need the Tables.txt and Introduction.txt table files from [A Mini Database Manager] to be in the server's launch directory. Here's the code for the server: ####### # # rpc-DB-srv Mini Database Manager server, based on Eolas' dp_RPC.tcl system # by Mike Doyle (mike@doyles.com) # also based on Richard Suchenwirth's Little Database API, Little Database Gui, # and Persistent array utility # ####### source dp_RPC.tcl set port 8088 console show wm withdraw . global db dir result port set dir [pwd] proc quit {} { stop_server exit } proc start_server {} { global port puts "t-DB Server started on port: [dp_MakeRPCServer $port]" #add approved client hostname -- one line for each host puts [dp_Host +localhost] puts [dp_Host +205.229.151.3] } proc stop_server {} { global port dp_CloseRPC $port puts "Server stopped on port $port" } proc persistentArray {arrName {filename {}}} { upvar 1 $arrName arr array set arr {} ;# to make sure it exists, and is an array if {$filename==""} {set filename $arrName.txt} set filename [file join [pwd] $filename] if [file exists $filename] { set fp [open $filename] array set arr [read $fp] close $fp } uplevel 1 [list trace var $arrName wu [list persist'save $filename]] } proc persist'save {filename arrName el op} { upvar 1 $arrName arr switch -- $op { w {set value $arr($el)} u {set value {}} } set fp [open $filename a] puts $fp [list $el $value] close $fp } proc db {table args} { upvar #0 $table db set key "" ;# in case args is empty foreach {- key item value} $args break set exists [info exists db($key)] set res {} switch [llength $args] { 0 { array set db {} ;# force to be an array interp alias {} $table {} db $table - set res $table } 1 {set res [array names db]} 2 {if {$key != ""} { if {$exists} {set res $db($key)} } else {array unset db} } 3 {if {$item != ""} { if {$exists} { set t $db($key) if {!([set pos [lsearch $t $item]]%2)} { set res [lindex $t [incr pos]] } } } elseif {$exists} {unset db($key)} } 4 { if {$exists} { if {!([set pos [lsearch $db($key) $item]]%2)} { if {$value != ""} { set db($key) [lreplace $db($key) [incr pos] $pos $value] } else {set db($key) [lreplace $db($key) $pos [incr pos]]} } elseif {$value != ""} { lappend db($key) $item $value } } elseif {$value != ""} {set db($key) [list $item $value]} set res $value ;# to be returned } default { if {[llength $args]%2} {error "non-paired item/value list"} foreach {item value} [lrange $args 2 end] { db $table - $key $item $value } } } set res } db Tables persistentArray Tables foreach i [lsort -dic [Tables]] {persistentArray $i; db $i} start_server # End server code ########################### And here's the code for the client: ####### # # rpc-DB-clnt Mini Database Manager client, based on Eolas' dp_RPC.tcl system # by Mike Doyle (mike@doyles.com) # also based on Richard Suchenwirth's Little Database API, Little Database Gui, # and Persistent array utility # ####### global host port server source dp_RPC.tcl set port 8088 # server ip or host name set server localhost namespace eval db::ui { variable topic "" } ;# required before procs can be defined proc db::ui::browse {table} { global record current_table host port set record "" set current_table $table set t [toplevel .tpl] wm title $t "Mike's Mini dp_RPC Database Manager v.0.5" wm protocol $t WM_DELETE_WINDOW {dp_CloseRPC $host; exit} dp_RPC $host db $table set m1 [frame $t.top] listbox $m1.lb -bg white -height 5 -yscrollcommand [list $m1.y1 set] bind $m1.lb [list db::ui::select %W %y Tables] bind $m1.lb {.tpl.main.lb delete 0 end; set current_table [.tpl.top.lb get @0,%y]; foreach i [lsort -dic [dp_RPC $host [.tpl.top.lb get @0,%y]]] {.tpl.main.lb insert end $i}; db::ui::htext2 .tpl.main.t [.tpl.top.lb get @0,%y] -yscrollcommand [list .tpl.main.y2 set]} scrollbar $m1.y1 -command [list $m1.lb yview] htexttop $m1.t Tables -yscrollcommand [list $m1.y2 set] scrollbar $m1.y2 -command [list $m1.t yview] eval pack [winfo children $m1] -side left -fill y pack $m1.t -fill both -expand 1 set b1 [frame $t.bottom1] button $b1.edit -text Edit -command {db::edit_table .tpl.bottom1.edit $record Tables} button $b1.new -text New -command {db::new_table .tpl.bottom1.new $record Tables} button $b1.del -text Delete -command {db::delete_table .tpl.bottom1.del $record Tables} label $b1.find -text Find: entry $b1.tofind bind $b1.tofind [list db::ui::find %W $m1.t Tables] button $b1.action -text " 0 " -background green -command {catch "console show"} eval pack [winfo children $b1] -side left -fill x pack $b1.tofind -expand 1 pack $b1 -side top -fill x foreach i [lsort -dic [dp_RPC $host Tables]] {$m1.lb insert end $i} set m [frame $t.main] listbox $m.lb -bg white -height 15 -yscrollcommand [list $m.y1 set] bind $m.lb [list db::ui::select %W %y $table] scrollbar $m.y1 -command [list $m.lb yview] htext $m.t $table -yscrollcommand [list $m.y2 set] scrollbar $m.y2 -command [list $m.t yview] eval pack [winfo children $m] -side left -fill y pack $m.t -fill both -expand 1 set b [frame $t.bottom] button $b.edit -text Edit -command {db::edit_record .tpl.bottom.edit $record $current_table} button $b.new -text New -command {db::new_record .tpl.bottom.new $record $current_table} button $b.del -text Delete -command {db::delete_record .tpl.bottom.del $record $current_table} label $b.find -text Find: entry $b.tofind bind $b.tofind [list db::ui::find %W $m.t $table] button $b.action -text " ! " -command {db::ui::callback $db::ui::topic} eval pack [winfo children $b] -side left -fill x pack $b.tofind -expand 1 pack $b -side bottom -fill x pack $m1 -fill both -expand 0 pack $m -fill both -expand 1 foreach i [lsort -dic [dp_RPC $host $table]] {$m.lb insert end $i} set t } proc db::ui::callback args {} ;# redefine this for specific action proc db::ui::htext {w table args} { global host eval text $w -bg grey90 -padx 3 -wrap word -height 7 -width 50 $args $w tag config title -font {Times 12 bold} $w tag config link -foreground blue -underline 1 $w tag bind link "$w config -cursor hand2" $w tag bind link "$w config -cursor {}" $w tag bind link [list db::ui::click %W %x %y $table] $w insert end \n\n$table\n\n title "Select topic from listbox" $w insert end "\n\n[llength [dp_RPC $host $table]] entries in table" set w } proc db::ui::htexttop {w table args} { eval text $w -bg grey90 -padx 3 -wrap word -height 7 -width 50 $args $w tag config title -font {Times 12 bold} $w tag config link -foreground blue -underline 1 $w tag bind link "$w config -cursor hand2" $w tag bind link "$w config -cursor {}" $w tag bind link [list db::ui::click %W %x %y $table] $w insert end \n\n$table\n\n title "Double-click table name at left to view its records" set w } proc db::ui::htext2 {w table args} { global host $w delete 1.0 end $w tag config title -font {Times 12 bold} $w tag config link -foreground blue -underline 1 $w tag bind link "$w config -cursor hand2" $w tag bind link "$w config -cursor {}" $w tag bind link [list db::ui::click %W %x %y $table] $w insert end \n\n$table\n\n title "Select topic from listbox" $w insert end "\n\n[llength [dp_RPC $host $table]] entries in table" bind .tpl.main.lb [list db::ui::select %W %y $table] set w } proc db::ui::click {w x y table} { set range [$w tag prevrange link [$w index @$x,$y]] if [llength $range] { Show $w [eval $w get $range] $table } } proc db::ui::select {w y table} { global record set record [$w get @0,$y] Show [winfo parent $w].t [$w get @0,$y] $table } proc db::ui::Show {w title table} { global host variable topic set topic $title $w delete 1.0 end $w insert end $title\n title \n set titles [dp_RPC $host $table] foreach {item value} [dp_RPC $host $table $title] { if {$item == "@" && [file exists $value]} { set img [image create photo -file $value] $w image create 1.0 -image $img $w insert 1.1 " " } else { $w insert end $item\t foreach word $value { if {[lsearch $titles $word]>=0} {set tag link} else {set tag {}} $w insert end $word $tag " " } } $w insert end \n } } proc db::ui::find {w textw table} { global host set tofind [$w get] set found {} foreach key [dp_RPC $host $table] { set data [dp_RPC $host $table $key] if [regexp -indices -nocase ($tofind) $data -> pos] { lappend found [list $key [lindex $pos 0] $data] } } switch [llength $found] { 0 {error "No match for $tofind"} 1 {Show $textw [lindex [lindex $found 0] 0] $table} default {choice $textw $table $tofind $found} } } proc db::ui::choice {w table tofind found} { $w delete 1.0 end $w insert end "Search results for '$tofind':\n" title \n foreach pair $found { foreach {title pos data} $pair break set context [string range $data [expr $pos-15] [expr $pos+25]] $w insert end $title link \t...$context...\n "" pos=$pos\n } } proc db::edit_record {w r table} { global record current_table host $w configure -text Done .tpl.bottom.del configure -state disabled .tpl.bottom.new configure -state disabled .tpl.bottom.find configure -text Record: .tpl.bottom.tofind delete 0 end .tpl.bottom.tofind insert end "{$table} {$r} [dp_RPC $host $table $r]" .tpl.bottom.edit configure -command {eval dp_RPC $host [.tpl.bottom.tofind get]; .tpl.bottom.tofind delete 0 end; .tpl.bottom.find configure -text Find: db::ui::Show .tpl.main.t $record $current_table .tpl.bottom.edit configure -text Edit .tpl.bottom.del configure -state active .tpl.bottom.new configure -state active .tpl.bottom.edit configure -command {db::edit_record .tpl.bottom.edit $record $current_table}} } proc db::new_record {w r table} { global record current_table host $w configure -text Done .tpl.bottom.del configure -state disabled .tpl.bottom.edit configure -state disabled .tpl.bottom.find configure -text Record: .tpl.bottom.tofind delete 0 end .tpl.bottom.tofind insert end "$table {RECORD} {FIELD} {VALUE}" .tpl.bottom.new configure -command {eval dp_RPC $host [.tpl.bottom.tofind get]; set record [lindex [.tpl.bottom.tofind get] 1] .tpl.main.lb delete 0 end; foreach i [lsort -dic [dp_RPC $host $current_table]] {.tpl.main.lb insert end $i}; .tpl.bottom.tofind delete 0 end; .tpl.bottom.find configure -text Find: .tpl.bottom.del configure -state active .tpl.bottom.edit configure -state active db::ui::Show .tpl.main.t $record $current_table .tpl.bottom.new configure -text New .tpl.bottom.new configure -command {db::new_record .tpl.bottom.new $record $current_table}} } proc db::delete_record {w r table} { global record current_table host $w configure -text Done .tpl.bottom.new configure -state disabled .tpl.bottom.edit configure -state disabled .tpl.bottom.find configure -text "Delete $r? Enter yes/no to confirm/cancel:" .tpl.bottom.tofind delete 0 end .tpl.bottom.del configure -command { if {[.tpl.bottom.tofind get] == "yes"} { .tpl.bottom.tofind delete 0 end .tpl.bottom.tofind insert end {$current_table $record ""} eval dp_RPC $host [.tpl.bottom.tofind get]; } .tpl.main.lb delete 0 end; foreach i [lsort -dic [dp_RPC $host $current_table]] {.tpl.main.lb insert end $i}; .tpl.bottom.tofind delete 0 end; .tpl.bottom.new configure -state active .tpl.bottom.edit configure -state active .tpl.bottom.find configure -text Find: .tpl.bottom.del configure -text Delete .tpl.main.t delete 1.0 end .tpl.bottom.del configure -command {db::delete_record .tpl.bottom.del $record $current_table} } } proc db::edit_table {w r table} { global record current_table host $w configure -text Done .tpl.bottom1.del configure -state disabled .tpl.bottom1.new configure -state disabled .tpl.bottom1.find configure -text Record: .tpl.bottom1.tofind delete 0 end .tpl.bottom1.tofind insert end "{$table} {$r} [dp_RPC $host $table $r]" .tpl.bottom1.edit configure -command {eval dp_RPC $host [.tpl.bottom1.tofind get]; .tpl.bottom1.tofind delete 0 end; .tpl.bottom1.find configure -text Find: db::ui::Show .tpl.top.t $record Tables .tpl.bottom1.edit configure -text Edit .tpl.bottom1.del configure -state active .tpl.bottom1.new configure -state active .tpl.bottom1.edit configure -command {db::edit_table .tpl.bottom1.edit $record Tables}} } proc db::new_table {w r table} { global record current_table host $w configure -text Done .tpl.bottom1.del configure -state disabled .tpl.bottom1.edit configure -state disabled .tpl.bottom1.find configure -text Record: .tpl.bottom1.tofind delete 0 end .tpl.bottom1.tofind insert end "$table {TABLENAME} Description: {DESCRIPTION TEXT}" $w configure -command {eval dp_RPC $host [.tpl.bottom1.tofind get]; set record [lindex [.tpl.bottom1.tofind get] 1] db $record persistentArray $record .tpl.top.lb delete 0 end; foreach i [lsort -dic [dp_RPC $host Tables]] {.tpl.top.lb insert end $i}; .tpl.bottom1.tofind delete 0 end; .tpl.bottom1.find configure -text Find: .tpl.bottom1.del configure -state active .tpl.bottom1.edit configure -state active db::ui::Show .tpl.top.t $record Tables .tpl.bottom1.new configure -text New .tpl.bottom1.new configure -command {db::new_record .tpl.bottom1.new $record Tables}} } proc db::delete_table {w r table} { global record current_table host $w configure -text Done .tpl.bottom1.new configure -state disabled .tpl.bottom1.edit configure -state disabled .tpl.bottom1.find configure -text "Delete $r? Enter yes/no to confirm/cancel:" .tpl.bottom1.tofind delete 0 end .tpl.bottom1.del configure -command { if {[.tpl.bottom1.tofind get] == "yes"} { .tpl.bottom1.tofind delete 0 end .tpl.bottom1.tofind insert end {Tables $record ""} eval [.tpl.bottom1.tofind get]; } .tpl.top.lb delete 0 end; foreach i [lsort -dic [dp_RPC $host Tables]] {.tpl.top.lb insert end $i}; .tpl.bottom1.tofind delete 0 end; .tpl.bottom1.new configure -state active .tpl.bottom1.edit configure -state active .tpl.bottom1.find configure -text Find: .tpl.bottom1.del configure -text Delete .tpl.top.t delete 1.0 end .tpl.bottom1.del configure -command {db::delete_table .tpl.bottom1.del $record $current_table} } } wm withdraw . if { [winfo exist .connect] == 0 } then { toplevel .connect wm title .connect "Remote Host:" frame .connect.f1 pack .connect.f1 -fill x pack [entry .connect.f1.e1 -width 20 -textvariable server] -side right pack [label .connect.f1.l1 -text "Hostname: "] -side right pack [frame .connect.f3] -fill x pack [button .connect.f3.b1 -text Clear -command { .connect.f1.e1 delete 0 end} ] -side left pack [button .connect.f3.b2 -text "Connect" -command { wm withdraw .connect set host [dp_MakeRPCClient $server $port] db::ui::browse Introduction }] -side left } #console show wm deiconify .connect # End client code ########################### Enjoy! [MDD] ----