'''[http://www.vex.net/~cthuang/tcom/%|%tcom]''', maintained by [Chin Huang] provides both client and server [COM] programmability. ** News ** [PO] 2012-11-17: See my [http://www.eurotcl.tcl3d.org/eurotcl-2012/presentations/12-Obermeier-Cawt.pdf%|%Work-In_Progress] presentation at the [10th European Tcl/Tk Users Meeting 2012%|%EuroTcl 2012]. The proposed Cawt package is still in work. For a preview send me a mail. ** Attributes ** current version: 3.9 website: http://www.vex.net/~cthuang/tcom/ ** See Also ** [Compiling TCom]: [Tcom examples for Microsoft Excel]: [TcomOffice]: [tcom Allows Emacs as Editor for MS Outlook]: [Web automatic testing using TCOM extention]: [Printing DYMO Labels with Tcl and tcom]: [Matthias Hoffmann - Tcl-Code-Snippets - tcom & wmi - Examples]: [tcom Allows Emacs as Editor for MS Outlook]: [MNO] I put together a really simple set of scripts/Emacs [Lisp] at: The functionality should be obvious from the title ;-) [How one discovers the API for a COM-exporting application]: [Stefan Vogel]'s page: explains on his page how to convert MSProject-Files to [CSV]-format and how to use the IE-object with callbacks via ::tcom::bind [Stefan Vogel]'s page: has interesting uses of bind [Howto export Microsoft Outlook contacts to XML using tcom and tDom]: [Running WEAP from Tcl with tcom]: An example for the water planning software WEAP: [COMet]: a [COM] explorer for Tcl that relies on tcom. Designed to discover and explore COM interfaces. [ADO (Visual Fox Pro oledb) - read .dbf with tcom]: [Advanced browser management]: an example of using the '''::tcom::ref getactiveobject''' command to get a reference to an already running instance of Internet Explorer. [http://en.wikipedia.org/wiki/The_Colour_of_Magic%|%tcom]: Another '''tcom''' (humor) [http://wfr.tcl.tk/1012%|%Instancier des objets COM avec Tcom]: [TCOM package - 800700a4 - No more threads can be created in the system.]: [tcom server]: Create com objects using tcl ** Download ** http://www.vex.net/~cthuang/tcom/ Since March 2002, tcom is a part of the [ActiveTcl] Batteries Included distribution. tcom was previously available as a static runtime, without the dependency on an external [DLL]. Any volunteers to provide that again? [http://www.wagner.pp.ru/~vitus/software/tcl/tcom.html%|%Improved TCOM extension], [Victor Wagner] [jbr] 2013-08-27: I've [https://github.com/jbroll/tcom%|%forked tcom] on github and added features to allow passing binary buffers as byte arrays. ** Installation ** Place tcom/lib/Banking and tcom/lib/tcom a location that Tcl looks in when searching for libraries. The binary requires MSVCP60.DLL, the Visual C++ 6.0 runtime. If another Windows application didn't already install it, see [http://support.microsoft.com/default.aspx?scid=kb;en-us;259403%|%How to obtain the Visual C++ 6.0 run-time components] At that point, it should be possible to ====== package require tcom ====== ** Description ** tcom is only available for Windows. Many of its users agree that "this package has been incredibly helpful" (to quote one [comp.lang.tcl] posting). ** Question: [TclScript] (Unanswered) ** Version 3.8 includes an experimental implementation of a Tcl [Active Script]ing engine. The engine is written mostly in [incr Tcl] using tcom's COM server framework. IIS might be able to interpret this [ASP]: ====== <%@ LANGUAGE = "TclScript" %> <% Response write "Hello world!" %> ====== [Chin Huang]: reports that using incr tcl 3.8b3, one can do the following in Internet Explorer: ======
======
which is likely enough support to replace JScript with Tcl.
[MHo] 2006-04-04: Is there a equivalent way for this to work with
'''Mozilla'''?
** Type Conversion **
[phk]: type conversion can be tricky
method Export needs a boolean value as argument
======none
$rep Export 0
> 0x80020005 {Unknown error}
======
As [Ronald Dauster] showed me, there is a way to force a var to be boolean type
(Tcl internally):
======
set FALSE false
if {$FALSE} {}
$rep Export $FALSE
======
----
[nl]: try this
======
proc FALSE {} {return [expr 1 == 0]}
proc TRUE {} {return [expr 1 == 1]}
proc MAYBE {} {return [expr ([clock seconds]%2) == 0]}; #:-)
$rep Export FALSE
======
[glennj]: You probably meant
======
$rep Export [FALSE]
======
** Null **
If you need a NULL, use the command:
: '''::tcom::null'''
It returns a token that tcom will interpret as a NULL.
** Speed Considerations **
[nurdglaw%|%Alan Grunwald%|%] 2012-11-14:
I use tcom now and again to export the contents of a [tablelist] to a Word
document and have always just gritted my teeth and put up with how slow it is.
However, today I discovered
[http://msdn.microsoft.com/en-us/library/aa537149(v=office.11).aspx%|%Automating
Word Tables for Data Insertion and Extraction], describing how to speed things
up by entering the data as delimited text and the converting to a table. It's
LOTS faster.
I also export [tablist] contents to [Excel], and doing them one cell at a time
is also ''really'' slow. Buoyed by my success with Word I found
[http://support.microsoft.com/kb/306022%|%How to transfer data to an Excel
workbook by using Visual Basic .NET], describing how to speed that up, which
looks as though it sends an array to a multi-cell range. Does anyone know how I
would set up a VB-style array from Tcl to use this method?
[APN]: The very latest [TcomOffice] package (0.5a3) has an example of this
(sending arrays to a cell range). Unfortunately I don't remember exactly where.
You will have to look through the examples and test scripts.
[nurdglaw%|%Alan Grunwald%|%]: Thanks Ashok, I'll have a look for the example.
** Terminating a COM Server **
Tip: To terminate a COM server cleanly, you must drop all references. If you
stored the handle in a local variable, the reference is released when execution
leaves the variable's scope. If you stored the handle in a global variable,
you can release the reference by unsetting the variable, setting the variable
to another value, or exiting the Tcl interpreter. Some applications, such as
Excel, require you explicitly terminate them by calling "$application Quit",
otherwise they continue to run even after dropping all references.
** Out-of-Process COM objects **
[Klaus Maria Hansen] asked about out-of-process COM objects. Chin Huang
answered that, yes, tcom can create these:
: From a tclsh, create the COM object and register it in the running object table using the "`::tcom::object create -registeractive`" command. Keep this tclsh running for as long as you want the COM object to be alive.
: To access your COM object, client programs must get a reference to it using the "`::tcom::ref getactiveobject`" command.
** Binary Data **
Laurent Riesterer 2004-03-25:
I had a struggle to have a COM object correctly accepting binary blob. Here is
a method which works
======
set data [binary format "II" 1 2]
append data "someinfo"
$comObject method $data
======
The [[`[binary format]`] object creates a ByteArray Tcl_Obj type and if you do
not convert it to a string (be careful to use '''string bytelength''' to get
the length as '''string length''' changes the type), then tcom smartly detects
the ByteArray and send a type VT_ARRAY|VT_UI1 to the COM object.
[PYK] 2013-10-19: Whatever the case may have been in the past, currently
[[`[string length]`] is smart enough to to force a string conversion of
ByteArray Tcl_obj types if they are pure (no associated string representation).
Therefore, if the length is needed, [[`[string length]`] is the thing to use.
** Empty String: Type Conflict **
Error '0x80020005 Type conflict' when using empty string as parameter
[HaO] 2012-12-18: When I call a method with an empty string, I often get the
error:
======
% $h SetValue {}
0x80020005 Typkonflikt
======
To cure this, one may use:
======
% $h SetValue [::tcom::variant bstr {}]
======
** ::tcom::import **
The '''::tcom::import''' command creates new commands using the type
information from a type library. The ::tcom::import command returns the
namespace in which it created the new commands. By default, the namespace is
the type library name. This example assumes ComponentABC.dll contains a type
library named ComponentABCLib:
======
% ::tcom::import ComponentABC.dll
ComponentABCLib
======
To list the commands ::tcom::import created:
======
% info commands ComponentABCLib::*
::ComponentABCLib::Interface1 ::ComponentABCLib::Interface2
::ComponentABCLib::ObjectXYZ
======
This command creates an object and obtains an interface pointer to the object's
default interface:
======
set object [::ComponentABCLib::ObjectXYZ]
======
Given an object reference, you can query for specific interfaces:
======
set interface1 [::ComponentABCLib::Interface1 $object]
set interface2 [::ComponentABCLib::Interface2 $object]
======
** Examples **
[http://www.trade-ideas.com/ForDevelopers/%|%trade ideas, for developers]: A complete example. This Tcl code uses tcom to receive, augment, and display streaming real-time data from an [ActiveX] control.
----
In a [c.l.t.] posting, [Chin Huang] offers this illuminating example of
use of tcom to retrieve Office document properties:
======
package require tcom
set filePropReader [::tcom::ref createobject DSOleFile.PropertyReader]
set properties [$filePropReader GetDocumentProperties {c:\tst.xls}]
puts [$properties Name]
puts [$properties AppName]
======
This example uses a COM object server which you can download from http://support.microsoft.com/support/kb/articles/Q224/3/51.ASP.
----
[Martin Lemburg] demonstrates with
======
% package require tcom
3.3
% set excel [::tcom::ref createobj Excel.Application]
::tcom::handle0x01337EF0
% $excel Run "Module!Macro" arg1 arg2 arg3 ... argn
result
======
how easy it is to invoke an [Excel] macro with Excel's built-in "Run" method.
He notes, however, that "The only complication is to find out the 'path' to
that macro. The XLA or XLM file or the workbook or worksheet containing the
macro must be opened or given in that path!"
Note that it's sometimes beneficial/necessary/advisable/... to
======
$excel Visible 1
======
----
Here's how to dump an Excel chart to an image file:
======
set excel [tcom::ref createobj Excel.Application]
set Workbooks [$excel Workbooks]
set Book [$Workbooks Open {C:\path\to\my_spreadsheet.xlsx}]
set worksheets [$Book Worksheets]
[[[[$worksheets Item "My charts"] ChartObjects] Item [expr 1]] Chart] Export {C:\some\path\dumpchart.png}
======
I'm sure the last line looks ugly to many people, so here is a suggestion:
======
proc evalChain {args} {
set argStr [join $args]
set newArgs [list]
while {[set idx [string first -> $argStr]] >= 0} {
lappend newArgs [string range $argStr 0 [expr {$idx - 1}]]
set argStr [string range $argStr [expr {$idx + 2}] end]
}
lappend newArgs $argStr
set result ""
foreach arg $newArgs {
set result [uplevel 1 $result $arg]
}
return $result
}
======
Now you can do:
======
evalChain $worksheets Item Chart->ChartObjects->Item \[expr 1\]->Chart->Export {C:\\some\\path\\dumpchart.png}
======
Hmmm, maybe not a huge improvement... :-(
----
Fabrice Pardo offers this slightly more elaborate example of Excel invocation:
======
set application [::tcom::ref createobject Excel.Application]
$application Visible 1
set workbooks [$application Workbooks]
set workbook [$workbooks Add]
set worksheets [$workbook Worksheets]
set worksheet [$worksheets Item [expr 1]]
set cells [$worksheet Cells]
set i 0
foreach row {1 2 3} {
foreach column {A B C} {
$cells Item $row $column [incr i]
}
}
$workbook SaveAs {c:\tst.xls}
$application Quit
======
----
[Sacha Schär] shows how to add something into an existing workbook:
======
package require tcom
set excel [::tcom::ref createobj Excel.Application]
#$excel Visible 1
set workbooks [$excel Workbooks]
set workbook [$workbooks Open {c:\tst.xls}]
set worksheets [$workbook Worksheets]
set worksheet [$worksheets Item [expr 1]]
set cells [$worksheet Cells]
$cells Item 2 B {my additional text}
$workbook Save
#$workbook SaveAs {c:\tst2.xls}
$excel Quit
======
----
Many [COM] methods declare specific data types for their parameters. For
example, if a method declares an '''int''' parameter, then tcom converts the
argument to the native integer representation when calling the method. Some
methods declare parameters of type '''VARIANT''', which can hold different data
types, such as string, int, or double. Hopefully, the method can interpret the
argument in whatever form of data type is passed in the VARIANT. However, some
methods interpret the argument based on the data type in the VARIANT. For
example, the Excel method to access a cell declares the column parameter as a
VARIANT. If a string is passed in the column parameter, the method considers
it to specify the name of a column (A, B, C, ...). If an int is passed, the
method interprets it as a numeric column index (based from 1). You might be
tempted to address a column in Excel by number:
======
$cells Item $row 1 "Hello"
======
This works if the internal representation of the literal value '''1''' is an
integer. In this case, tcom passes an integer value to the Item method, which
considers the integer value to be a numeric column index. However, the
bytecode compiler keeps a single copy of each literal value in a Tcl script or
procedure body. Suppose you execute a command which changes the internal
representation of the literal value 1 to a string. If you invoke the Item
method after that, tcom passes a string consisting of the single character "1"
to the method. The Item method interprets a string argument as the name of a
column (A, B, C, ...), but there is no column named "1", so it returns error
code 0x800a03ec. It's good style to address columns using letters instead. If
you really want to address columns by number, you can ensure you're passing an
integer value by doing:
======
$cells Item $row [expr 1] "Hello"
======
[Lars H]: This sounds like a classical example of what goes wrong when
an extension ignores the [everything is a string] paradigm. If the string 1
and the int 1 may have different semantics, then there should on the Tcl
side be distinct strings representing the two (e.g. "string 1" and "int 1",
probably with a preferred interpretation as lists). Heuristics for trying to
figure out what was the intent for an ambiguous value such as "1" are OK,
but they must not depend on the internal representation.
[HaO] 2012-09-03: maybe, the command `::tcom::variant` may help (see [tcom server]):
======
$cells Item $row [::tcom::variant int 1] "Hello"
======
----
Another example from Chin Huang, in this case to add a (new) worksheet at the
end of the workbook:
======
package require tcom
set application [::tcom::ref createobject "Excel.Application"]
set workbooks [$application Workbooks]
set workbook [$workbooks Add]
set worksheets [$workbook Worksheets]
set lastWorksheet [$worksheets Item [$worksheets Count]]
set worksheet [$worksheets Add [::tcom::na] $lastWorksheet]
======
** Example: Open MSWord **
[David Bigelow] explained in [comp.lang.tcl] how to open MSWord with tcom:
======
package require tcom
set ::application [::tcom::ref createobject "Word.Application"]
$::application Visible 1
set ::docs [$::application Documents]
set ::doc [$::docs Open "[pwd]/test.doc" [::tcom::na] [expr 1]]
# NOTE: the [expr 1] option in this example toggles the file to read only.
======
** Example: Insert Table into Word **
[Miko] howto insert a table in a new Word document:
======
set ::application [::tcom::ref createobject "Word.Application"]
$::application Visible 1
set ::docs [$::application Documents]
set ::doc [$::docs Add ]
#ActiveDocument.Tables.Add Range:=Selection.Range, NumRows:=2, NumColumns:= 3
#translation:
[$::doc Tables] Add [[$::application Selection] Range] [set NumRows 2] [set NumColumns 3]
======
** Example: Extract Data from [Excel] **
[AM] 2004-05-11: I have always stayed far away from COM objects and the like,
but as users keep asking about ways to import MS Excel files to get their data
into a user-interface, I decided to have a look at ''tcom''. With the help of
this page, I came up with the following simple script to extract data from a
haphazard Excel file:
======
# Extract some cells from an Excel file
#
package require tcom
set app [::tcom::ref getobject [file nativename [file join [pwd] "test_tcom.xls"]]]
set ws [$app Worksheets]
set ws1 [$ws Item [expr 1]]
set cells [$ws1 Cells]
foreach row {1 2 3 4} {
foreach col {A B C} {
set cell [$cells Item $row $col]
puts "Row/column ($row/$col): [$cell Value]"
}
}
$app Close
======
Some comments:
* MS Excel complained about the file not existing at first: it does not "know" about the current directory that the Tcl script is using.
* Most tcom commands return some sort of handle, so you have to go down the object hierarchy to finally get at the data.
But once you are over this hurdle, it is very nice to be able to deal with such
arcane stuff! (The real problem now is: what data are going to be passed from
the Excel sheet to my user-interface?)
** Example: Create a Chart In [Excel] **
[ramsan] proposes how to create a chart in [Excel] (assuming you have already
connected with the commands found previously in this page):
======
set chart [[$application Charts] Add]
$chart ChartType 65 ;# xlLineMarkers
# this is equal to: set range [$cells Range "D4:G7"]
set range [$cells Range [$cells Item 1 [expr 4]] [$cells Item 5 [expr 7]]]
$chart SetSourceData $range 2 ;# xlColumns
set xrange [$cells Range [$cells Item 2 [expr 3]] [$cells Item 5 [expr 3]]]
for { set i [expr 1] } { $i <= 4 } { incr i } {
[[$chart SeriesCollection] Item $i] XValues $xrange
}
set axe [[$chart Axes] Item 1] ;# xlPrimary
$axe HasTitle True
[[$axe AxisTitle] Characters] Text "Nº nodos"
set axe [[$chart Axes] Item 2] ;# xlSecondary
$axe MinimumScaleIsAuto True
$axe MaximumScale 0.04
$axe HasTitle True
[[$axe AxisTitle] Characters] Text "Error(%)"
$chart HasTitle True
[[$chart ChartTitle] Characters] Text "Comparación resultados"
#$chart Location 2 "Hoja1" ;# xlLocationAsObject
[$chart ChartArea] Copy ;# copy the image of the chart to clipboard
======
When you want to modify the example, you will need to learn additional Excel
com functions and constants. You can learn more inside Excel
Tools->Macros->Visual Basic Editor and check the Help or the objects browser.
Maybe you need to install additional help from the installation disk.
** Example: Close [Excel] **
[PataElmo] 2007-11-28: I have some interesting code for handling Excel through
the tcom interface. This little proc allows you to control [Excel] close to
how you would in VB. Could also be useful for other microsoft VB Apps.
======
proc ExcelCmd {App command} {
set clist [split [regsub -all {\(([^()]+)\)} $command { \1}] .]
set string $App
while {[llength $clist]>0} {
if {[llength [lindex $clist 0]]>1} {
if {[expr {! [catch {expr {int([lindex [lindex $clist 0] 1])}}]}]} {
set string [$string [lindex [lindex $clist 0] 0] [expr [lindex [lindex $clist 0] 1]]]
} else {
set string [$string [lindex [lindex $clist 0] 0] [lindex [lindex $clist 0] 1]]
}
} else {
set string [$string [lindex $clist 0]]
}
set clist [lreplace $clist 0 0]
}
return $string
}
======
Usage:
======
set App [::tcom::ref createobject Excel.Application]
ExcelCmd $App Visible(0)
ExcelCmd $App Workbooks.Add
ExcelCmd $App Workbooks.Item(1).Worksheets.Item(2).Delete
ExcelCmd $App Workbooks.Item(1).Worksheets.Item(2).Delete
ExcelCmd $App Workbooks.Item(1).Worksheets.Item(1).Name("First")
ExcelCmd $App Visible(1)
======
----
[Tyzio] 2008-01-09: I took the code from [PataElmo] and improved it a little
bit to be more universal:
======
proc ExcelCmd {{App ""} {command ""} } {
if {$App eq "" && $command eq ""} {
return -7
}
if {$App eq ""} {
set App [::tcom::ref createobject Excel.Application]
} elseif {$command eq ""} {
set command $App
set App [::tcom::ref createobject Excel.Application]
}
# Remove ()
set command [regsub -all {\(([^()]+)\)} $command { \1}]
# Clean the command line to have good quote chars and remove colons which are not string part
set temp [regsub -all {(\".*),(.*\")} $command \\1\xd7\\2]
set temp [string map [list {, } { } {,} { } \xd7 {,} {\"} \\" \" {\"}] $temp]
set clist [split $temp .]
set string $App
while {[llength $clist]>0} {
if {[llength [lindex $clist 0]]>1} {
foreach c [lrange [lindex $clist 0] 1 end] {
if {[string is integer $c]} {
lappend cl "\[expr $c\]"
} else {
lappend cl $c
}
}
set string [eval $string [lindex $clist 0 0] [join $cl]]
unset cl
} else {
set string [$string [lindex $clist 0]]
}
set clist [lreplace $clist 0 0]
}
return $string
}
======
Usage (Create Pie Chart):
======
set App [::tcom::ref createobject Excel.Application]
set chtOb [ExcelCmd $App {ActiveSheet.ChartObjects.Add(100, 100, 250, 175)}]
ExcelCmd $chtOb {Chart.ChartType(5)}
ExcelCmd $chtOb {Chart.SeriesCollection.NewSeries}
set serCol [ExcelCmd $chtOb {Chart.SeriesCollection(1)}]
set range [ExcelCmd $App {ActiveSheet.Range(A2,F2).Value}]
$serCol XValues $range
set range [ExcelCmd $App {ActiveSheet.Range(A3,F3).Value}]
$serCol Values $range
$serCol Name {CHART PIE}
======
** Example: ROT object **
`christopher.owens` at `analog.com` 2004-04-14: It looks as though all you need
to do to communicate with an object from the ROT (Windows' "Running Object
Table") is use
======
::tcom::ref getactiveobject "Appname.Application"
======
instead of
======
::tcom::ref createobject "Appname.Application"
======
You apparently would want to do this in cases where the object is a [DLL]
that's already in use by an application, but also presents a [COM] interface.
** Example: Microsoft Dictionary Object **
[Scott Nichols]:
Below is an example on how to access Microsoft's VB dictionary object using
tcom. It is very similar to [Perl]'s associative array or Tcl's array, except
it seems to only support one value per key. I have not tested its performance.
======
package require tcom
# Create the Dictionary -- Similar to a PERL associative array.
set d [::tcom::ref createobj scripting.dictionary]
# Add some key value pairs
$d Add keyboard Logitech
$d Add motherboard Asus
# Check if an item exists then add it.
if { ! [$d Exists memory] } {$d Add memory PNY}
# Retrieve all items
set items [$d Items]
# Retrieve all keys
set keys [$d Keys]
# Replace an Item's value with a new value
if { [$d Exists motherboard]} {$d Item motherboard HP}
======
For more information on how to use Microsoft's Dictionary object please read:
http://www.rickclark.org/articles/dicobj.pdf
** Example: Outlook Contacts **
[LEM]: This piece of code retrieves the full name of contact number one in your
Outlook contacts list. Tested on Outlook 2002.
======
package require tcom
set outlook [::tcom::ref getactiveobject Outlook.Application]
set outlookinfo [::tcom::info interface $outlook]
# Prints long list of available methods, one of which is GetNamespace
$outlookinfo methods
#Magic string MAPI found on MSDN - only valid argument
set ns [$outlook GetNamespace "MAPI"]
set nsinfo [::tcom::info interface $ns]
#Prints long list of methods, GetDefaultFolder is one of them
$nsinfo methods
# The magic number 10 means the Contact folder
set contacts [$ns GetDefaultFolder 10]
set items [$contacts Items]
set item [$items Item 1]
set iteminfo [::tcom::info interface $item]
# Prints long list of available properties, including FullName
$iteminfo properties
$item -get FullName
======
** Example: Outlook Contacts (2) **
[LEM]: I have elaborated somewhat on my Outlook Contacts code
======
# Features:
# Starts Outlook if not already running.
# Retrieves all your contacts and creates one button for each
# If you make a change to your contacts the gui updates
# Tested on:
# TCOM 3.8, TCL/TK 8.4.7, WinXP, Outlook 2002
# Bugs:
# I believe the handle passed to the "item" procedure is
# dead because tcom automatically release references when
# moving out of a variable's scope.
proc log {string} {if {$::debug} {.t insert end "$string"}}
# Two procedures to see which properties and methods are available on an object
proc getproperties {handle} {set infohandle [::tcom::info interface $handle];$infohandle properties}
proc getmethods {handle} {set infohandle [::tcom::info interface $handle];$infohandle methods}
proc eventhandler {args} {
set event [lindex $args 0]
set handle [lindex $args 1]
if {$event == "ItemAdd"} {item add $handle
} elseif {$event == "ItemChange"} {item change $handle
} elseif {$event == "ItemRemove"} {item remove $handle
} else {
log "Unrecognized event: $args\n"
}
}
proc item {action handle} {
log "Event: $action, $handle\n"
# Could do something smart, but for now just redraw GUI completely
draw_gui .f
}
proc init_outlook {} {
if {[catch {set outlook [::tcom::ref getactiveobject "Outlook.Application"]} msg]} {
log "Outlook not running, trying to start\n"
if {[catch {set outlook [::tcom::ref createobject "Outlook.Application"]} msg]} {
log "Failed starting Outlook: $msg\n"
} else {
log "Outlook started OK\n"
}
}
# The magic string MAPI was found on MSDN - only valid argument
set ns [$outlook GetNamespace "MAPI"]
# The magic number 10 means the Contact folder, refer MSDN
set contacts [$ns GetDefaultFolder 10]
# Must keep items global, if not the reference is released by tcom
set ::items [$contacts Items]
# Bind to changes on the items collection
::tcom::bind $::items eventhandler
}
proc draw_gui {w} {
if {[winfo exists $w.f]} {
destroy $w.f
}
pack [frame $w.f] -fill x -expand yes
set i 0
# $items is a handle to a collection object. Thus we can use ::tcom::foreach.
::tcom::foreach item $::items {
pack [button $w.f.b[incr i] -anchor w -text [$item -get FullName]] -side top -fill x -expand yes
}
}
package require tcom
set debug 1
pack [frame .f] -fill x
if {$::debug} {pack [text .t] -fill both -expand yes}
init_outlook
draw_gui .f
wm title . Contacts
bind .