By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
445,677 Members | 1,177 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 445,677 IT Pros & Developers. It's quick & easy.

foreach listviewitem in a background thread

P: n/a
How can I have access to the items collection of a listview control on
my form from a background thread?

I know I need delegates to update the listview control and I have those
calls in the foreach loop, but I'm not sure how to access the items
collection.

foreach (ListViewItem li in this.listview1.Items)
{
Invoke(new deleg(updatectrl), new object [] { param });
}

Thanks!
May 15 '07 #1
Share this Question
Share on Google+
13 Replies


P: n/a
On Tue, 15 May 2007 14:53:01 -0500, deciacco <a@awrote:
>How can I have access to the items collection of a listview control on
my form from a background thread?

I know I need delegates to update the listview control and I have those
calls in the foreach loop, but I'm not sure how to access the items
collection.

foreach (ListViewItem li in this.listview1.Items)
{
Invoke(new deleg(updatectrl), new object [] { param });
}

Thanks!
out of my head: you execute a method Safemethod, if it's called from
another thread InvokeRequired will be true and de method will be
called again but this time from within the UI thread, and the foreach
loop can be used safely.
private void SafeMethod ( )
{
if ( this.InvokeRequired )
this.Invoke( new SafeMethodDelegate ( SafeMethod ) );
else
{
foreach (ListViewItem li in this.listview1.Items)
{
// do sth with li
}
}
}

private delegate void SafeMethodDelegate ( );

--
Ludwig
http://www.coders-lab.be
May 15 '07 #2

P: n/a
I don't think this will work for me, because I don't want the foreach to
be executed on the main thread. I need the foreach to stay in it's own
thread and inside the foreach have calls to thread-safe methods. The
problem is that I need the worker thread to have information about the
listbox so it can pass on information to the thread safe functions to do
their jobs.

For example, for each one of the items in the list I want a worker
thread to call setImageForListViewItem and pass in the current item
index and the index of the image to use for that item.

foreach (ListViewItem li in this.listviewProgress.Items)
{
Invoke(new setImageForListViewItemDelg(setImageForListViewIte m), new
object[] { 2, li.Index });
}

Thanks.

Ludwig wrote:
On Tue, 15 May 2007 14:53:01 -0500, deciacco <a@awrote:
>How can I have access to the items collection of a listview control on
my form from a background thread?

I know I need delegates to update the listview control and I have those
calls in the foreach loop, but I'm not sure how to access the items
collection.

foreach (ListViewItem li in this.listview1.Items)
{
Invoke(new deleg(updatectrl), new object [] { param });
}

Thanks!

out of my head: you execute a method Safemethod, if it's called from
another thread InvokeRequired will be true and de method will be
called again but this time from within the UI thread, and the foreach
loop can be used safely.
private void SafeMethod ( )
{
if ( this.InvokeRequired )
this.Invoke( new SafeMethodDelegate ( SafeMethod ) );
else
{
foreach (ListViewItem li in this.listview1.Items)
{
// do sth with li
}
}
}

private delegate void SafeMethodDelegate ( );


May 15 '07 #3

P: n/a
On Tue, 15 May 2007 14:40:44 -0700, deciacco <a@awrote:
I don't think this will work for me, because I don't want the foreach to
be executed on the main thread. I need the foreach to stay in it's own
thread and inside the foreach have calls to thread-safe methods. The
problem is that I need the worker thread to have information about the
listbox so it can pass on information to the thread safe functions to do
their jobs.
Can you clarify what about the goal is giving you trouble?

I see two possible issues, at least. One is synchronizing access to the
Items collection. The other is making sure your delegate gets invoked on
the ListView control for each item. But since as far as I know you're not
trying to modify the collection while the thread runs, and you *do* show
us a call to Invoke in your example, I don't really see what the problem
is. (The Invoke appears to be on the owning control, probably a Form, but
that should put you on the correct thread anyway).

What exactly is it you want the worker thread to do that you're not able
to do, and why aren't you able to do it (the second part of that question
is as important as the first IMHO)?

Pete
May 15 '07 #4

P: n/a
Ok...I'm having trouble explaining myself...
Here is the end result:

I have a list of items in a listview control. Each item in the list is a
task that the worker thread needs to accomplish. When the work thread is
on a list item I want the worker thread to change the icon of that list
item to show "currently executing". After that task is executed I want
the worker thread to change the item's icon to show "task completed
sucessfully" or "task had a problem".
Peter Duniho wrote:
On Tue, 15 May 2007 14:40:44 -0700, deciacco <a@awrote:
>I don't think this will work for me, because I don't want the foreach
to be executed on the main thread. I need the foreach to stay in it's
own thread and inside the foreach have calls to thread-safe methods.
The problem is that I need the worker thread to have information about
the listbox so it can pass on information to the thread safe functions
to do their jobs.

Can you clarify what about the goal is giving you trouble?

I see two possible issues, at least. One is synchronizing access to the
Items collection. The other is making sure your delegate gets invoked
on the ListView control for each item. But since as far as I know
you're not trying to modify the collection while the thread runs, and
you *do* show us a call to Invoke in your example, I don't really see
what the problem is. (The Invoke appears to be on the owning control,
probably a Form, but that should put you on the correct thread anyway).

What exactly is it you want the worker thread to do that you're not able
to do, and why aren't you able to do it (the second part of that
question is as important as the first IMHO)?

Pete
May 15 '07 #5

P: n/a
On Tue, 15 May 2007 15:31:09 -0700, deciacco <a@awrote:
Ok...I'm having trouble explaining myself...
Here is the end result:

I have a list of items in a listview control. Each item in the list isa
task that the worker thread needs to accomplish. When the work thread is
on a list item I want the worker thread to change the icon of that list
item to show "currently executing". After that task is executed I want
the worker thread to change the item's icon to show "task completed
sucessfully" or "task had a problem".
Okay. :)

So, the basic idea IMHO is that each thread should include as part of its
state information the ListViewItem you want to update (initialized when
you start the thread, using the usual thread initialization mechanisms).
Then, when you want to update the visual state of the ListViewItem, call
Invoke() or BeginInvoke() on the ListView itself (which you can get from
the ListViewItem), to execute a delegate that updates the ListViewItem as
you desire.

For example:

private ListViewItem _lvi;

private void _StartWork()
{
_lvi.ListView.Invoke(delegate () { _lvi.ImageIndex = kiimgWorking;
});
}

private void _EndWork()
{
_lvi.ListView.Invoke(delegate () { _lvi.ImageIndex =
kiimgCompleted; });
}

(where you've set up an appropriate ImageList containing your icons, and
the kiimgWorking and kiimgCompleted constants correspond to the
appropriate image indices)

You'd call _StartWork() at the beginning of your worker thread processing
method, and _EndWork() at the end.

If that doesn't help, then you'll probably have to provide more details.

Pete
May 15 '07 #6

P: n/a
Peter,
Thanks so much for your reply, I will give it a try and report back.
Really appreciate the help with this stuff!!

Peter Duniho wrote:
On Tue, 15 May 2007 15:31:09 -0700, deciacco <a@awrote:
>Ok...I'm having trouble explaining myself...
Here is the end result:

I have a list of items in a listview control. Each item in the list is
a task that the worker thread needs to accomplish. When the work
thread is on a list item I want the worker thread to change the icon
of that list item to show "currently executing". After that task is
executed I want the worker thread to change the item's icon to show
"task completed sucessfully" or "task had a problem".

Okay. :)

So, the basic idea IMHO is that each thread should include as part of
its state information the ListViewItem you want to update (initialized
when you start the thread, using the usual thread initialization
mechanisms). Then, when you want to update the visual state of the
ListViewItem, call Invoke() or BeginInvoke() on the ListView itself
(which you can get from the ListViewItem), to execute a delegate that
updates the ListViewItem as you desire.

For example:

private ListViewItem _lvi;

private void _StartWork()
{
_lvi.ListView.Invoke(delegate () { _lvi.ImageIndex =
kiimgWorking; });
}

private void _EndWork()
{
_lvi.ListView.Invoke(delegate () { _lvi.ImageIndex =
kiimgCompleted; });
}

(where you've set up an appropriate ImageList containing your icons, and
the kiimgWorking and kiimgCompleted constants correspond to the
appropriate image indices)

You'd call _StartWork() at the beginning of your worker thread
processing method, and _EndWork() at the end.

If that doesn't help, then you'll probably have to provide more details.

Pete
May 16 '07 #7

P: n/a
Peter,

This is what I'm trying to do:
When the form loads, the listbox is populated with the tasks.
Then a new thread is started to go through those tasks one by one in order.

private delegate void setStatusImageDelegate(int imgIndex, int listItemIndex);

Thread t = new Thread(new ThreadStart(executeTasksInOrder));
t.IsBackground = true;
t.Start();

private void executeTasksInOrder()
{
foreach (ListViewItem li in this.listviewProgress.Items) //<--Can't access listview from thread
{
this.Invoke(new setStatusImageDelegate(setStatusImage), new object[] { 0, li.Index} );
Thread.Sleep(2000); // <--For testing
this.Invoke(new setStatusImageDelegate(setStatusImage), new object[] { 2, li.Index });
}
}

private void setStatusImage(int imgIndex, int listItemIndex)
{
this.listviewProgress.Items[listItemIndex].ImageIndex = imgIndex;
}
Peter Duniho wrote:
On Tue, 15 May 2007 15:31:09 -0700, deciacco <a@awrote:
>Ok...I'm having trouble explaining myself...
Here is the end result:

I have a list of items in a listview control. Each item in the list is
a task that the worker thread needs to accomplish. When the work
thread is on a list item I want the worker thread to change the icon
of that list item to show "currently executing". After that task is
executed I want the worker thread to change the item's icon to show
"task completed sucessfully" or "task had a problem".

Okay. :)

So, the basic idea IMHO is that each thread should include as part of
its state information the ListViewItem you want to update (initialized
when you start the thread, using the usual thread initialization
mechanisms). Then, when you want to update the visual state of the
ListViewItem, call Invoke() or BeginInvoke() on the ListView itself
(which you can get from the ListViewItem), to execute a delegate that
updates the ListViewItem as you desire.

For example:

private ListViewItem _lvi;

private void _StartWork()
{
_lvi.ListView.Invoke(delegate () { _lvi.ImageIndex =
kiimgWorking; });
}

private void _EndWork()
{
_lvi.ListView.Invoke(delegate () { _lvi.ImageIndex =
kiimgCompleted; });
}

(where you've set up an appropriate ImageList containing your icons, and
the kiimgWorking and kiimgCompleted constants correspond to the
appropriate image indices)

You'd call _StartWork() at the beginning of your worker thread
processing method, and _EndWork() at the end.

If that doesn't help, then you'll probably have to provide more details.

Pete
May 16 '07 #8

P: n/a
On Wed, 16 May 2007 07:55:08 -0700, deciacco <a@awrote:
This is what I'm trying to do:
When the form loads, the listbox is populated with the tasks.
Then a new thread is started to go through those tasks one by one in
order.
Ahh...I see. I misunderstood, and thought you had one thread per task.
And now I see why you're having so much trouble. You get the cross-thread
access not allowed exception when you try to access the collection of
Items in the ListView object.

For what it's worth, you might have been more clear about that (as in,
actually describe the error you got :) ).

Anyway, now that we're on the same page...

You can address the issue in a variety of ways. You could use Invoke to
go back and forth, using it to get the count of items and then to get each
individual one based on an index. Or you could just build the list of the
items and pass that to the thread when you initialized is (using the
parameter start version of the thread constructor and start method). Or
you could build an "enumerator by proxy" into the form's class that would
allow you to use the IEnumerable interface to handle the foreach()
transparently (that interface would handle the invoking required to
extract the necessary item at the appropriate moment).

Here's an example of the first method I describe (note that this is the
first time I've had to deal with ref or out parameters using the Invoke
method, so I can't vouch that I've done it in the best or cleanest
way...but it does work :) ):

private delegate void SetIndexHandler();
private delegate void GetItemHandler(int iitem, out ListViewItem
item);
private delegate void GetCountHandler(out int count);

private void _MyThread()
{
int citem;
object[] parmsGetCount = new object[1];
object[] parmsGetItem = new object[2];

// Use Invoke to get the current count of items in the list
listView1.Invoke(new GetCountHandler(delegate(out int
countOut) { countOut = listView1.Items.Count; }), parmsGetCount);
citem = (int)parmsGetCount[0];

for (int iitem = 0; iitem < citem; iitem++)
{
ListViewItem lvi;

// Use Invoke to get a ListViewItem given the current index
// Note: this doesn't do any checking to make sure the
list
// hasn't changed since you got the count. In real code
you
// would probably want to range check the input :).
parmsGetItem[0] = iitem;
listView1.Invoke(new GetItemHandler(delegate(int iitemGet,
out ListViewItem itemOut) { itemOut = listView1.Items[iitemGet]; }),
parmsGetItem);
lvi = (ListViewItem)parmsGetItem[1];

// Use Invoke to actually change the image index
listView1.Invoke(new SetIndexHandler(delegate()
{ lvi.ImageIndex = 0; }));
Thread.Sleep(2000);
listView1.Invoke(new SetIndexHandler(delegate()
{ lvi.ImageIndex = 1; }));
}
}

Of course, the above code requires that you've set up your ListView with
the appropriate image list and items. I didn't bother to paste in all of
the rest of the code for the form, but hopefully you get the idea.

Pete
May 16 '07 #9

P: n/a
Thank you so much...this is great!
Just one thing I don't understand.

If I were to change your code a little and do something like this:

************
private delegate void GetCountHandler(out int count);

this.listviewProgress.Invoke(new GetCountHandler(getListViewCount), parmsGetCount);

private void getListViewCount(out int countOut)
{
countOut = this.listviewProgress.Items.Count;
}
************

Everything is fine, works great.

The same is not true of this line of code:
I know it works and I understand that you are creating the delegate on the fly, but
is there a way to do it like above?

************
this.listviewProgress.Invoke(new SetIndexHandler(delegate() { lvi.ImageIndex = 0; }));
************

I was thinking something like this:

************
private delegate void setScriptStatusImageDelegate(int imgIndex, int listItemIndex);

this.listviewProgress.Invoke(new setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0, lvi.Index });

private void setScriptStatusImage(int imgIndex, int listItemIndex)
{
this.listviewProgress.Items[listItemIndex].ImageIndex = imgIndex;
}
************

The problem is, I still get the cross-thread error on lvi.Index.
I thought I should be able to pass in lvi.Index since I used a delegate to get lvi.
Peter Duniho wrote:
On Wed, 16 May 2007 07:55:08 -0700, deciacco <a@awrote:
>This is what I'm trying to do:
When the form loads, the listbox is populated with the tasks.
Then a new thread is started to go through those tasks one by one in
order.

Ahh...I see. I misunderstood, and thought you had one thread per task.
And now I see why you're having so much trouble. You get the
cross-thread access not allowed exception when you try to access the
collection of Items in the ListView object.

For what it's worth, you might have been more clear about that (as in,
actually describe the error you got :) ).

Anyway, now that we're on the same page...

You can address the issue in a variety of ways. You could use Invoke to
go back and forth, using it to get the count of items and then to get
each individual one based on an index. Or you could just build the list
of the items and pass that to the thread when you initialized is (using
the parameter start version of the thread constructor and start
method). Or you could build an "enumerator by proxy" into the form's
class that would allow you to use the IEnumerable interface to handle
the foreach() transparently (that interface would handle the invoking
required to extract the necessary item at the appropriate moment).

Here's an example of the first method I describe (note that this is the
first time I've had to deal with ref or out parameters using the Invoke
method, so I can't vouch that I've done it in the best or cleanest
way...but it does work :) ):

private delegate void SetIndexHandler();
private delegate void GetItemHandler(int iitem, out ListViewItem
item);
private delegate void GetCountHandler(out int count);

private void _MyThread()
{
int citem;
object[] parmsGetCount = new object[1];
object[] parmsGetItem = new object[2];

// Use Invoke to get the current count of items in the list
listView1.Invoke(new GetCountHandler(delegate(out int
countOut) { countOut = listView1.Items.Count; }), parmsGetCount);
citem = (int)parmsGetCount[0];

for (int iitem = 0; iitem < citem; iitem++)
{
ListViewItem lvi;

// Use Invoke to get a ListViewItem given the current index
// Note: this doesn't do any checking to make sure the
list
// hasn't changed since you got the count. In real code
you
// would probably want to range check the input :).
parmsGetItem[0] = iitem;
listView1.Invoke(new GetItemHandler(delegate(int
iitemGet, out ListViewItem itemOut) { itemOut =
listView1.Items[iitemGet]; }), parmsGetItem);
lvi = (ListViewItem)parmsGetItem[1];

// Use Invoke to actually change the image index
listView1.Invoke(new SetIndexHandler(delegate() {
lvi.ImageIndex = 0; }));
Thread.Sleep(2000);
listView1.Invoke(new SetIndexHandler(delegate() {
lvi.ImageIndex = 1; }));
}
}

Of course, the above code requires that you've set up your ListView with
the appropriate image list and items. I didn't bother to paste in all
of the rest of the code for the form, but hopefully you get the idea.

Pete
May 17 '07 #10

P: n/a
On Thu, 17 May 2007 08:23:26 -0700, deciacco <a@awrote:
[...]
I was thinking something like this:

************
private delegate void setScriptStatusImageDelegate(int imgIndex, int
listItemIndex);

this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0,
lvi.Index });

private void setScriptStatusImage(int imgIndex, int listItemIndex)
{
this.listviewProgress.Items[listItemIndex].ImageIndex = imgIndex;
}
************

The problem is, I still get the cross-thread error on lvi.Index.
I thought I should be able to pass in lvi.Index since I used a delegate
to get lvi.
Nope. Sorry. :) Going through Invoke (note that it's the Invoke that's
important, not the delegate...though Invoke does of course require a
delegate) is what prevents the cross-thread problem, and it applies only
during the actual execution of Invoke. You get the same object back via
Invoke as you would calling directly, and all the same rules apply to that
object.

(By the way, there *are* mechanisms that actually do convert certain kinds
of objects from one execution environment to another, whether that's
cross-thread, cross-process, cross-network, etc...it's known as
"marshaling". So it wasn't unreasonable of you to think maybe what you
wanted to do was possible. It just turns out that in this case, what's
being "marshaled" is the code execution, not any of the data being used)..

You can get the reference to the ListViewItem and handle that safely in
the thread, but any operations on it have to follow the same rules as
apply to the ListView object itself. So, you can Invoke to get the
reference to it, and then you can pass that reference back to the UI
thread via Invoke for doing something else, but you can't actually *do*
anything with the reference on the worker thread.

Fortunately, none of this is a big problem. You may have already figured
out the solution, but just in case...

If you want to pass an index rather than the ListViewItem itself, you can
just change the code to look like this:

private delegate void setScriptStatusImageDelegate(int imgIndex, int
listItemIndex);

this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0,
iitem });

private void setScriptStatusImage(int imgIndex, int listItemIndex)
{
this.listviewProgress.Items[listItemIndex].ImageIndex = imgIndex;
}
and then the loop looks like this:

for (int iitem = 0; iitem < citem; iitem++)
{
// Use Invoke to actually change the image index
this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0,
iitem });
Thread.Sleep(2000);
this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0,
iitem });
}

The main problem being that you are now even more exposed to the
possibility of the ListView changing between calls. Not that that problem
didn't exist before, but at least if there was a bug, you would still
always update the icon for the same ListViewItem (though by the time you
get to updating it, it would be possible that it wasn't the ListViewItem
corresponding to the processing you're doing, depending on how you
correlate your ListViewItem instances when the processing iteration).
With the above code, if there's a bug you could actually wind up setting
the icon 0 for one ListViewItem and then setting the icon 1 for a
different ListViewItem later.

Obviously the best solution is to not have bugs where you modify or
otherwise lose correlation with the ListView while it's still in use. :)
Just something to be aware of. The two versions of the code aren't
functionally equivalent, but as long as you understand the differences, I
think that's fine.

Pete
May 17 '07 #11

P: n/a
That was a very long explanation, so thank you for your time...great help!!!

Peter Duniho wrote:
On Thu, 17 May 2007 08:23:26 -0700, deciacco <a@awrote:
>[...]
I was thinking something like this:

************
private delegate void setScriptStatusImageDelegate(int imgIndex, int
listItemIndex);

this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage ), new object[] { 0,
lvi.Index });

private void setScriptStatusImage(int imgIndex, int listItemIndex)
{
this.listviewProgress.Items[listItemIndex].ImageIndex = imgIndex;
}
************

The problem is, I still get the cross-thread error on lvi.Index.
I thought I should be able to pass in lvi.Index since I used a
delegate to get lvi.

Nope. Sorry. :) Going through Invoke (note that it's the Invoke
that's important, not the delegate...though Invoke does of course
require a delegate) is what prevents the cross-thread problem, and it
applies only during the actual execution of Invoke. You get the same
object back via Invoke as you would calling directly, and all the same
rules apply to that object.

(By the way, there *are* mechanisms that actually do convert certain
kinds of objects from one execution environment to another, whether
that's cross-thread, cross-process, cross-network, etc...it's known as
"marshaling". So it wasn't unreasonable of you to think maybe what you
wanted to do was possible. It just turns out that in this case, what's
being "marshaled" is the code execution, not any of the data being used).

You can get the reference to the ListViewItem and handle that safely in
the thread, but any operations on it have to follow the same rules as
apply to the ListView object itself. So, you can Invoke to get the
reference to it, and then you can pass that reference back to the UI
thread via Invoke for doing something else, but you can't actually *do*
anything with the reference on the worker thread.

Fortunately, none of this is a big problem. You may have already
figured out the solution, but just in case...

If you want to pass an index rather than the ListViewItem itself, you
can just change the code to look like this:

private delegate void setScriptStatusImageDelegate(int imgIndex, int
listItemIndex);

this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0,
iitem });

private void setScriptStatusImage(int imgIndex, int listItemIndex)
{
this.listviewProgress.Items[listItemIndex].ImageIndex = imgIndex;
}
and then the loop looks like this:

for (int iitem = 0; iitem < citem; iitem++)
{
// Use Invoke to actually change the image index
this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0,
iitem });
Thread.Sleep(2000);
this.listviewProgress.Invoke(new
setScriptStatusImageDelegate(setScriptStatusImage) , new object[] { 0,
iitem });
}

The main problem being that you are now even more exposed to the
possibility of the ListView changing between calls. Not that that
problem didn't exist before, but at least if there was a bug, you would
still always update the icon for the same ListViewItem (though by the
time you get to updating it, it would be possible that it wasn't the
ListViewItem corresponding to the processing you're doing, depending on
how you correlate your ListViewItem instances when the processing
iteration). With the above code, if there's a bug you could actually
wind up setting the icon 0 for one ListViewItem and then setting the
icon 1 for a different ListViewItem later.

Obviously the best solution is to not have bugs where you modify or
otherwise lose correlation with the ListView while it's still in use.
:) Just something to be aware of. The two versions of the code aren't
functionally equivalent, but as long as you understand the differences,
I think that's fine.

Pete
May 17 '07 #12

P: n/a
On Thu, 17 May 2007 10:41:41 -0700, deciacco <a@awrote:
That was a very long explanation, so thank you for your time...great
help!!!
"Feed a man a fish, teach a man to fish..." :)

I could've just written your code for you, but I hope in the long run you
know more now. The long-term goal is, of course, to get you to the point
where *you* are answering *my* questions. :)
May 17 '07 #13

P: n/a
I agree with you completely...

I don't want the code written for me, I want someone to help me
understand, but it's so difficult to find people willing to do that and
it is difficult to communicate through newsgroups or email.

Peter Duniho wrote:
On Thu, 17 May 2007 10:41:41 -0700, deciacco <a@awrote:
>That was a very long explanation, so thank you for your time...great
help!!!

"Feed a man a fish, teach a man to fish..." :)

I could've just written your code for you, but I hope in the long run
you know more now. The long-term goal is, of course, to get you to the
point where *you* are answering *my* questions. :)
May 23 '07 #14

This discussion thread is closed

Replies have been disabled for this discussion.