Welcome Guest!
 VB Helper
 Previous Message All Messages Next Message 
VB Helper Newsletter  Rod Stephens
 Jul 30, 2009 12:11 PDT 

Sorry for the long hiatus. I've been working way too much (and getting
paid way too little ;-).

One of the things I've been doing is writing an article about 3-D
graphics in WPF. The first of two parts is available here:

    WPF Wonders: 3D Drawing
    http://www.devx.com/dotnet/Article/42370

This part explains the basics of 3-D drawing in WPF and provides some
very simple examples. The second part explains textures and shows how to
make some pretty cool scenes with objects made from bricks, wood, and so
forth.

It also draws a simple 3-D maze with a spinning globe in it.
Unfortunately there's no control code to let you walk through the
maze--just code to let you look at the maze from various angles. Perhaps
I'll have time to add maze exploration some day.
----------
Have a great week and thanks for subscribing!

Rod
RodSte-@vb-helper.com

Books To Keep: http://www.BooksToKeep.com
----------
*** Now Available ***

Beginning Database Design Solutions
http://www.amazon.com/exec/obidos/ASIN/0470385499/vbhelper/

Visual Basic 2008 Programmer's Reference
http://www.amazon.com/exec/obidos/ASIN/0470182628/vbhelper/
==========

    VB.NET Contents:
1. New HowTo: Display two forms, one showing pictures in a random order
in Visual Basic .NET
2. Updated HowTo: Make a drawing application in VB .NET
==========
++++++++++
<VB.NET>
++++++++++
==========
1. New HowTo: Display two forms, one showing pictures in a random order
in Visual Basic .NET
http://www.vb-helper.com/howto_net_random_picture_forms.html
http://www.vb-helper.com/HowTo/howto_net_random_picture_forms.zip

When the first form appears, it creates an instance of the second form
and displays it. It also positions the two forms.

Private PictureForm As Form2

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
    ' Show the second form.
    PictureForm = New Form2
    PictureForm.Show()

    Me.Location = New Point(10, 10)

    PictureForm.Location = New Point( _
        (Screen.PrimaryScreen.WorkingArea.Width - PictureForm.Width) \
2, _
        (Screen.PrimaryScreen.WorkingArea.Height - PictureForm.Height) \
2)
End Sub

When the second form appears, it loads the pictures from its startup
directory (or the source directory if the program is running in the
debugger). It also makes a PictureBox for each picture. It finishes by
calling RandomizePictures to display the pictures in a random order.

Private Pictures As New List(Of Image)
Private PictureBoxes As New List(Of PictureBox)

Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
    ' Get the source directory.
    Dim dir_name As String = Application.StartupPath
    If dir_name.EndsWith("\bin\Debug") Then dir_name =
dir_name.Substring(0, dir_name.Length - 10)

    ' Load the pictures.
    Dim dir_info As New DirectoryInfo(dir_name)
    For Each file_info As FileInfo In dir_info.GetFiles("*.jpg")
        Pictures.Add(Image.FromFile(file_info.FullName))

        ' Make a PictureBox for the picture.
        Dim pic As New PictureBox()
        pic.Parent = Me
        pic.SizeMode = PictureBoxSizeMode.AutoSize
        pic.Top = 5
        PictureBoxes.Add(pic)
    Next file_info

    ' Display the pictures.
    RandomizePictures()
End Sub

Subroutine RandomizePictures makes a new list and copies the pictures
into it in a random order. It then displays the pictures in the form's
PictureBoxes.

' Notice that this is Public so the other form can use it.
Public Sub RandomizePictures()
    ' Randomize the list.
    Dim rand As New Random()
    Dim new_list As New List(Of Image)
    Do While Pictures.Count > 0
        ' Move a random Image to the new list.
        Dim i As Integer = rand.Next(0, Pictures.Count)
        new_list.Add(Pictures(i))
        Pictures.RemoveAt(i)
    Loop

    Pictures = new_list

    ' Display the pictures.
    Dim max_height As Integer = 0
    Dim x As Integer = 5
    For i As Integer = 0 To Pictures.Count - 1
        PictureBoxes(i).Image = Pictures(i)
        PictureBoxes(i).Left = x
        x += PictureBoxes(i).Width + 5
        If max_height < PictureBoxes(i).Bottom Then max_height =
PictureBoxes(i).Bottom
    Next i

    Me.ClientSize = New Size(x + 5, max_height + 5)
End Sub

Finally, because the RandomizePictures subroutine is declared Public,
the code in the first form can call it. When you click the form's
Randomize button, it uses the following code to re-randomize the
pictures.

' Make the picture form randomize its pictures.
Private Sub btnRandomize_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnRandomize.Click
    PictureForm.RandomizePictures()
End Sub
==========
2. Updated HowTo: Make a drawing application in VB .NET
http://www.vb-helper.com/howto_net_drawing_framework.html
http://www.vb-helper.com/HowTo/howto_net_drawing_framework.zip

I added printing support to this example.
=====
This is a very large application so only some key points are described
here.

The Drawable class represents a drawing object (line, rectangle, etc.)
that can be specified by a bounding rectangle. It cannot handle things
such as curves, free-form drawing, and arbitrary polygons. This class
defines the following MustOverride routines. Their names and comments
should make them self-explanatory.


' Draw the object on this Graphics surface.
Public MustOverride Sub Draw(ByVal gr As Graphics)

' Return the object's bounding rectangle.
Public MustOverride Function GetBounds() As Rectangle

' Return True if this point is on the object.
Public MustOverride Function IsAt(ByVal x As Integer, ByVal y As
Integer) As Boolean

' The user is moving one of the object's points.
Public MustOverride Sub NewPoint(ByVal x As Integer, ByVal y As Integer)

' Return True if the object is empty (e.g. a zero-length line).
Public MustOverride Function IsEmpty() As Boolean


The DrawableLine, DrawableRectangle, DrawableEllipse, and DrawableStar
classes inherit from Drawable. They implement these routines
appropriately for their type of drawing. For example,
DrawableRectangle's Draw method draws a rectangle and its IsAt function
returns True if a specified point lies within the rectangle.

The module Geometry.vb contains routines for working with the class's
shapes. It has functions for calculating the distance between two
points, or between a point and a line segment. It also has a function
that can tell if a point lies inside an ellipse.
=====
When the user presses the mouse down, the program creates a new object
based on the currently selected tool.

The MouseMove event handler passes the new object information about the
mouse's position by calling its NewPoint method. That method moves the
second corner of the object's bounding rectangle.

When the user releases the mouse, the MouseUp event handler stops
drawing the new object and, if the object is not empty, adds it to the
picture.


' Perform an action depending on the currently pushed tool.
Private Sub picCanvas_MouseDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles picCanvas.MouseDown
    ' See which button was pressed.
    If e.Button = MouseButtons.Right Then
        ' Right button. See if we're drawing something.
        If m_NewDrawable Is Nothing Then
            ' We are not drawing. Ignore this button.
        Else
            ' We are drawing something. Cancel it.
            m_Picture.Remove(m_NewDrawable)

            m_NewDrawable = Nothing
            RemoveHandler picCanvas.MouseMove, AddressOf
NewDrawable_MouseMove
            RemoveHandler picCanvas.MouseUp, AddressOf
NewDrawable_MouseUp

            ' Redraw to erase the new object.
            picCanvas.Invalidate()
        End If
    Else
        ' Left button. See which tool is pushed.
        Select Case m_SelectedToolButton.ToolTipText
            Case "Pointer"
                ' Select an object.
                m_Picture.SelectObjectAt(e.X, e.Y)
            Case "Line"
                ' Start drawing a line.
                m_NewDrawable = New DrawableLine(m_CurrentForeColor,
m_CurrentLineWidth, e.X, e.Y, e.X, e.Y)
                m_Picture.Add(m_NewDrawable)
                AddHandler picCanvas.MouseMove, AddressOf
NewDrawable_MouseMove
                AddHandler picCanvas.MouseUp, AddressOf
NewDrawable_MouseUp
            Case "Rectangle"
                ' Start drawing a rectangle.
                m_NewDrawable = New
DrawableRectangle(m_CurrentForeColor, m_CurrentFillColor,
m_CurrentLineWidth, e.X, e.Y, e.X, e.Y)
                m_Picture.Add(m_NewDrawable)
                AddHandler picCanvas.MouseMove, AddressOf
NewDrawable_MouseMove
                AddHandler picCanvas.MouseUp, AddressOf
NewDrawable_MouseUp
            Case "Ellipse"
                ' Start drawing an ellipse.
                m_NewDrawable = New DrawableEllipse(m_CurrentForeColor,
m_CurrentFillColor, m_CurrentLineWidth, e.X, e.Y, e.X, e.Y)
                m_Picture.Add(m_NewDrawable)
                AddHandler picCanvas.MouseMove, AddressOf
NewDrawable_MouseMove
                AddHandler picCanvas.MouseUp, AddressOf
NewDrawable_MouseUp
            Case "Star"
                ' Start drawing a star.
                m_NewDrawable = New DrawableStar(m_CurrentForeColor,
m_CurrentFillColor, m_CurrentLineWidth, e.X, e.Y, e.X, e.Y)
                m_Picture.Add(m_NewDrawable)
                AddHandler picCanvas.MouseMove, AddressOf
NewDrawable_MouseMove
                AddHandler picCanvas.MouseUp, AddressOf
NewDrawable_MouseUp
        End Select

        ' Redraw.
        picCanvas.Invalidate()
    End If
End Sub

' On mouse move, continue drawing.
Private Sub NewDrawable_MouseMove(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs)
    ' Update the new line's coordinates.
    m_NewDrawable.NewPoint(e.X, e.Y)

    ' Redraw to show the new line.
    picCanvas.Invalidate()
End Sub

' On mouse up, finish drawing the new object.
Private Sub NewDrawable_MouseUp(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs)
    ' No longer watch for MouseMove or MouseUp.
    RemoveHandler picCanvas.MouseMove, AddressOf NewDrawable_MouseMove
    RemoveHandler picCanvas.MouseUp, AddressOf NewDrawable_MouseUp

    ' See if the new object is empty (e.g. a zero-length line).
    If m_NewDrawable.IsEmpty() Then
        ' Discard this object.
        m_Picture.Remove(m_NewDrawable)
    End If

    ' We're no longer working with the new object.
    m_NewDrawable = Nothing

    ' Redraw.
    picCanvas.Invalidate()
End Sub


This program also shows how to make a ToolBar dropdown that displays
images. The following code shows how this works for the line width
dropdown. The form's Load event handler starts the process by calling
subroutine MakeLineThicknessImages.

MakeLineThicknessImages saves the current number of images in the
imlToolbarButtons ImageList control. If then draws a series of images
showing different line thicknesses and adds them to the ImageList.

At design time, I gave the program a context menu containing menu items
named mnuThick1, mnuThick2, and so forth. The context menu is associated
with the thickness ComboBox-style ToolBar button.

The program calls subroutine PrepareThicknessMenu for each of these
thickness menus. This routine adds Click, MeasureItem, and DrawItem
event handlers for the menu item. It also sets the item's OwnerDraw
property to True so it generates the MeasureItem and DrawItem events.


' Get ready.
Private Sub Form1_Load(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.Load
    ...
    ' Make line color images.
    MakeLineThicknessImages()

    ' Set line thickness menu event handlers.
    PrepareThicknessMenu(mnuThick1)
    PrepareThicknessMenu(mnuThick2)
    PrepareThicknessMenu(mnuThick3)
    PrepareThicknessMenu(mnuThick4)
    PrepareThicknessMenu(mnuThick5)
    mnuThick1.PerformClick()
    ...
End Sub

' Make line thickness images.
Private Sub MakeLineThicknessImages()
    Dim bm As New Bitmap(16, 16)
    Dim gr As Graphics = Graphics.FromImage(bm)

    m_FirstLineThicknessImage = imlToolbarButtons.Images.Count
    For i As Integer = 1 To 5
        gr.Clear(SystemColors.Control)
        gr.DrawLine(New Pen(Color.Black, i), 0, 8, 16, 8)
        imlToolbarButtons.Images.Add(bm)
    Next i
End Sub

' Add Click, MeasureItem, and DrawItem event handlers
' to this MenuItem.
Private Sub PrepareThicknessMenu(ByVal menu_item As MenuItem)
    AddHandler menu_item.Click, AddressOf mnuLineThick_Click
    AddHandler menu_item.MeasureItem, AddressOf mnuLineThick_MeasureItem
    AddHandler menu_item.DrawItem, AddressOf mnuLineThick_DrawItem
    menu_item.OwnerDraw = True
End Sub


The mnuLineThick_Click event handler finds the menu item object that
raised the Click event and checks its Text property to get the item's
selected thickness. It sets the ComboBox ToolBar button's image to the
one with the right thickness. It then sets the thickness for the
currently selected object, if there is one.

The mnuLineThick_MeasureItem event handler tells VB thaht the menu item
needs a 16x16 pixel area. VB will allocate a space at least this large.
In practice, the area will be wider than this.

The mnuLineThick_DrawItem event handler determines whetrher the menu
item is currently selected (the mouse is over it) and picks appropriate
colors. It erases the menu item's background and then draws a line with
the correct thickness.


' The user has selected a new line thickness.
Private Sub mnuLineThick_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs)
    ' Update the current pen.
    Dim menu_item As MenuItem = DirectCast(sender, MenuItem)
    m_CurrentLineWidth = Integer.Parse(menu_item.Text)

    ' Update the toolbar display.
    tcboThickness.ImageIndex = m_CurrentLineWidth + _
        m_FirstLineThicknessImage - 1

    ' Update the selected object if there is one.
    If Not (m_Picture.SelectedDrawable Is Nothing) Then
        m_Picture.SelectedDrawable.LineWidth = m_CurrentLineWidth
        picCanvas.Invalidate()
    End If

    ' Reselect the currently selected tool.
    m_SelectedToolButton.Pushed = True
End Sub

' Allow room for the MenuItem.
Private Sub mnuLineThick_MeasureItem(ByVal sender As Object, ByVal e As
System.Windows.Forms.MeasureItemEventArgs)
    e.ItemWidth = 16
    e.ItemHeight = 16
End Sub

' Draw the menu item.
Private Sub mnuLineThick_DrawItem(ByVal sender As Object, ByVal e As
System.Windows.Forms.DrawItemEventArgs)
    Dim menu_item As MenuItem = DirectCast(sender, MenuItem)
    Dim thickness As Integer = Integer.Parse(menu_item.Text)

    ' See if we're selected.
    Dim fg_pen As Pen
    Dim bg_brush As Brush
    If (e.State And DrawItemState.Selected) = 0 Then
        ' Not selected.
        ' Use a light background and dark foreground.
        fg_pen = New Pen(SystemColors.MenuText, thickness)
        bg_brush = New SolidBrush(SystemColors.Menu)
    Else
        ' Selected.
        ' Use a dark background and light foreground.
        fg_pen = New Pen(SystemColors.HighlightText, thickness)
        bg_brush = New SolidBrush(SystemColors.Highlight)
    End If

    ' Erase the background.
    e.Graphics.FillRectangle(bg_brush, e.Bounds)

    ' Draw the line.
    Dim y As Integer = e.Bounds.Y + e.Bounds.Height \ 2
    e.Graphics.DrawLine(fg_pen, e.Bounds.X, y, e.Bounds.Right, y)

    fg_pen.Dispose()
    bg_brush.Dispose()
End Sub


This program also shows how to save and restore pictures. Each of the
drawing classes, including the DrawablePicture class that holds the
objects in a picture, has a Serializable attribute. That tells VB to
include information that allows an XmlSerializer object to serialize the
class.

The classes include other attributes to tell the serializer how to treat
certain properties. For instance, the following code shows part of the
Drawable class. Attributes indicate that the ForeColor, FillColor, and
IsSelected properties should not be serialized. LineWidth, X1, Y1, X2,
and Y2 should be serialized as attributes of the Drawable object, not as
separate elements inside it in the XML result.


Imports System.Xml.Serialization

<Serializable()> _
Public MustInherit Class Drawable
    ' Drawing characteristics.
    <XmlIgnore()> Public ForeColor As Color
    <XmlIgnore()> Public FillColor As Color
    <XmlAttributeAttribute()> Public LineWidth As Integer = 0

    <XmlAttributeAttribute()> Public X1 As Integer
    <XmlAttributeAttribute()> Public Y1 As Integer
    <XmlAttributeAttribute()> Public X2 As Integer
    <XmlAttributeAttribute()> Public Y2 As Integer

    ' Indicates whether we should draw as selected.
    <XmlIgnore()> Public IsSelected As Boolean = False
    ...
End Class


There are a couple of tricks here. Some properties are not serialized
because they are not needed when you reload a picture. There's no need
to remember whether an object was selected when you saved the file.

Some VB and system objects such as Color do not include information for
serialization. If you try to serialize one, you get an empty element. To
work around this, the Drawable class provides public properties that
represent its colors as ARGB values. Those are integers so they are easy
to serialize. The following code shows the property procedures for the
ForeColor.


<XmlAttributeAttribute("ForeColor")> _
Public Property ForeColorArgb() As Integer
    Get
        Return ForeColor.ToArgb()
    End Get
    Set(ByVal Value As Integer)
        ForeColor = Color.FromArgb(Value)
    End Set
End Property


The DrawablePicture class stores the picture's objects in an ArrayList
object. To serialize an ArrayList, you need to use the XmlElement
attribute to tell VB what types of objects the ArrayList might contain.


' The list where we will store objects.
<XmlElement(GetType(Drawable)), _
XmlElement(GetType(DrawableLine)), _
XmlElement(GetType(DrawableRectangle)), _
XmlElement(GetType(DrawableEllipse)), _
XmlElement(GetType(DrawableStar))> _
Public Drawables As New ArrayList


The DrawablePicture class's SavePicture and LoadPicture methods save and
load picture serializations. SavePicture makes an XmlSerializer
initialized to work with DrawablePicture objects. It makes a
StreamWriter attached to the output file and uses the XmlSerializer to
serialize the picture into it.

LoadPicture makes an XmlSerializer initialized to work with
DrawablePicture objects. It makes a StreamWriter attached to the input
file and uses the XmlSerializer to read a DrawablePicture object out of
it. It then returns this new object.


' Save the picture into the file.
Public Sub SavePicture(ByVal file_name As String)
    Try
        Dim xml_serializer As New
XmlSerializer(GetType(DrawablePicture))
        Dim stream_writer As New StreamWriter(file_name)
        xml_serializer.Serialize(stream_writer, Me)
        stream_writer.Close()
    Catch ex As Exception
        If MessageBox.Show(ex.Message & vbCrLf & _
            "Show internal error?", "Save Error", _
            MessageBoxButtons.YesNo, MessageBoxIcon.Question) =
DialogResult.Yes Then
            MessageBox.Show(ex.InnerException.ToString, "Internal
Error", _
                 MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        End If
    End Try
End Sub

' Laod the picture from the file.
Public Shared Function LoadPicture(ByVal file_name As String) As
DrawablePicture
    Try
        Dim xml_serializer As New
XmlSerializer(GetType(DrawablePicture))
        Dim file_stream As New FileStream(file_name, FileMode.Open)
        Dim new_picture As DrawablePicture = _
            DirectCast(xml_serializer.Deserialize(file_stream),
DrawablePicture)
        file_stream.Close()
        Return new_picture
    Catch ex As Exception
        If MessageBox.Show(ex.Message & vbCrLf & _
            "Show internal error?", "Save Error", _
            MessageBoxButtons.YesNo, MessageBoxIcon.Question) =
DialogResult.Yes Then
            MessageBox.Show(ex.InnerException.ToString, "Internal
Error", _
                 MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        End If
        Return Nothing
    End Try
End Function


To let the user move objects, the program looks for MouseMove events. If
the left button is down, the main form calls the picture's
MoveSelectedDrawableToMouse method to move the currently selected
Drawable. It then invalidates the picture to redraw it.


' If we have an object selected, move it.
Private Sub picCanvas_MouseMove(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles picCanvas.MouseMove
    ' Only move if the left button is down.
    If e.Button = Windows.Forms.MouseButtons.Left Then
        ' Move it.
        m_Picture.MoveSelectedDrawableToMouse(e.X, e.Y)

        ' Redraw to show the new position.
        picCanvas.Invalidate()
    End If
End Sub


The DrawablePicture's MoveSelectedDrawableToMouse method exits if no
Drawable is selected.

If an object is selected, the program calculates the distance the mouse
has been moved and calls the selected Drawable's MoveRelative method to
move it by a corresponding amount. It then saves the new mouse position
for future moves.

(Note: The mouse's position is saved in (m_SelectedMouseX,
m_SelectedMouseY) when a Drawable is selected so it is ready to move.)


' Move the selected drawable. The mouse has moved from
' (m_SelectedMouseX, m_SelectedMouseY) to (x, y).
Public Sub MoveSelectedDrawableToMouse(ByVal x As Integer, ByVal y As
Integer)
    ' Do nothing if nothing is selected.
    If SelectedDrawable Is Nothing Then Exit Sub

    ' See how far we want it moved.
    Dim new_dx As Integer = x - m_SelectedMouseX
    Dim new_dy As Integer = y - m_SelectedMouseY

    ' Move it.
    SelectedDrawable.MoveRelative(new_dx, new_dy)

    ' Save the new mouse position.
    m_SelectedMouseX = x
    m_SelectedMouseY = y
End Sub


The program uses a PrintDocument and PrintPreviewDialog to print the
drawing and to display a print preview. The most interesting part of
this code is the PrintPage event handler that the is called to generate
the printout.

The actual printing is done by a simple call to:

    m_Picture.Draw(e.Graphics)

The rest of the code scales the picture to fill the printable area and
translates it to center the result.


    Private Sub pdPrint_PrintPage(ByVal sender As System.Object, ByVal e
As System.Drawing.Printing.PrintPageEventArgs) Handles pdPrint.PrintPage
#Const PRINT_CENTERED = True
#Const PRINT_ENLARGED = True
        '#Const PRINT_MARGIN = True

#If PRINT_CENTERED Then ' Center the picture.
        ' Get the picture's bounds.
        Dim bounds As Rectangle = m_Picture.GetBounds()

        ' Translate the drawing to the origin.
        e.Graphics.TranslateTransform(-bounds.X, -bounds.Y)

        Dim scale As Single = 1

#If PRINT_ENLARGED Then ' Scale to fit.
        Dim xscale As Double = 1
        If bounds.Width > 0 Then xscale = e.MarginBounds.Width /
bounds.Width
        Dim yscale As Double = 1
        If bounds.Height > 0 Then yscale = e.MarginBounds.Height /
bounds.Height

        If xscale > yscale Then
            scale = CSng(yscale)
        Else
            scale = CSng(xscale)
        End If
        e.Graphics.ScaleTransform(scale, scale,
Drawing2D.MatrixOrder.Append)
#End If

        ' Translate to center the drawing.
        Dim cx As Integer = CInt((e.MarginBounds.Width - bounds.Width *
scale) / 2)
        Dim cy As Integer = CInt((e.MarginBounds.Height - bounds.Height
* scale) / 2)
        e.Graphics.TranslateTransform( _
            e.MarginBounds.X + cx, _
            e.MarginBounds.Y + cy, _
            Drawing2D.MatrixOrder.Append)
#End If

        ' Draw the picture.
        m_Picture.Draw(e.Graphics)

#If PRINT_MARGIN Then
        ' Draw the margin.
        e.Graphics.ResetTransform()
        Using margin_pen As New Pen(Color.Red)
            margin_pen.DashPattern = New Single() {5, 5}
            e.Graphics.DrawRectangle(margin_pen, e.MarginBounds)
        End Using
#End If
    End Sub


This example also shows how to change the stacking order of the objects
it draws, and how to delete objects. See the code for additional
details.
==========
Archives:
    http://www.topica.com/lists/VBHelper
    http://www.topica.com/lists/VB6Helper
    http://www.topica.com/lists/VBNetHelper

Post questions at:
    http://www.topica.com/lists/VBHelperQA
	
 Previous Message All Messages Next Message 
  Check It Out!

  Topica Channels
 Best of Topica
 Art & Design
 Books, Movies & TV
 Developers
 Food & Drink
 Health & Fitness
 Internet
 Music
 News & Information
 Personal Finance
 Personal Technology
 Small Business
 Software
 Sports
 Travel & Leisure
 Women & Family

  Start Your Own List!
Email lists are great for debating issues or publishing your views.
Start a List Today!

© 2001 Topica Inc. TFMB
Concerned about privacy? Topica is TrustE certified.
See our Privacy Policy.