TabControl and PictureBox problem

smiles

Well-known member
Joined
May 21, 2008
Messages
47
Programming Experience
Beginner
I first attempt my coding for one pictureBox (named pictureBox1), I select a link from listbox (path to an image) and that image appears in pictureBox1.
Now I intend to use tabControl (10 tabs maximum) and each tab contains a pictureBox so I can view many images when navigate to each tab.
But I am wonder if I have to repeat my code for 1st tab to 10th tab :(
Could you help me with a more convenient way to solve it :)
Thanks so much !!!
 
Well, it appears that what you are trying to do is dynamically allot different tabs and a contained PictureBox for each item they click. Let us go with a base level form:

Form1
  • ListBox1
  • TabControl1 (with one page TabPage1)
    • PictureBox1 - Docked to Fill TabPage
  • ContextMenu

the List Box I would Dock to the Left side, whether manually setting the anchors or adjusting as you would, or just using the Dock Property.

The TabControl1 I would similarly Dock but to the right Side of the form, again using either the Dock property or manually settings it's constraints/anchor points. Associate the Context Menu with the TabControl.

Now the Context menu Will have One Menu Item which Says "Close Tab"

this is the fun part:

VB.NET:
sub ListBox1_Click(<standard parameters>) Handles ListBox1.Click
  if ListBox1.SelectedIndex > -1 then
     TabControl1.TabPages(0).Select
     PictureBox1.ImageLocation = ListBox1.SelectedValue
     PictureBox1.Load()
  end if
end sub

sub ListBox1_DoubleClick(<standard parameter>) handles ListBox1.MouseDoubleClick
  if Listbox1.SelectedIndex > -1 then
    dim p as new picturebox()
    TabControl1.TabPages.Add("TabPage" & TabControl1.Count + 1, "Tab Caption", 0)
    with TabControl1.TabPages(TabControl1.Count - 1)
       .Select 'selects the current tab
       .Controls.Add(p) 'adds teh picturebox
       p.Dock = System.Windows.Forms.DockStyle.Fill 'sets the picture box to fill
    end with
    p.ImageLocation = ListBox1.SelectedValue
    p.Load()
  end if
end sub

sub CloseTabMenuItem_Click(<standard parameter>) handles CloseTabMenuItem.Click
   if (TabControl1.TabCount > 1) andalso (TabControl1.SelectedIndex > 0) then
      TabControl1.TabPages.RemoveAt(TabControl1.SelectedIndex)
   end if
end sub

The Basics of this design here would allow for a single click to load the new image into the default (first) picture box, but the double click would cause a new tab to open up in the TabControl, add a PictureBox to it and load the new image, with the Context Menu to allow the user to get rid of unwanted tabs on the fly.

This more or less would work, however I would recommend that you utilize this within your own design, to better understand how the controls are created and assigned, as well you may not be using ListBox.SelectedValue to store the file names, etc, so you'll probably need to reconfigure the above code to process the actual file name appropriatly. You can also do MouseDown events instead of the Click/DoubleClick, as well you can check "Control.ModifierKeys" to instead do a CTRL+CLICK to open a new tab, instead of a double click.

And Finally, you can also update the design to say a single click (or select) loads the image into the "Active" tab, by:
VB.NET:
  with TabControl1.SelectedTab
    if typeof .Controls(0) is PictureBox then
      dim p as picturebox = DirectCast(.Controls(0), PictureBox)
      p.ImageLocation = ListBox1.SelectedValue
      p.Load
    end if
  end with
which would allow the user even further control of how many tabs they want opened to view the images.

Hope this helps,
 
Hi! Thanks for your hint, I will do researching on it but I have to finish the last affair with my tabcontrol, that is how can I put a delete sign on each tab then when I click on it, that tab will close (instead of double click or any others ...) :confused:
I am in stalemate with it :eek: Could you help me a way. Thanks so much !!!
 
For each TabPage you can select an ImageIndex that uses the TabControl's Imagelist list of images, so that will get you the delete sign. However,
The only way that I could see how to click the image for some effect is to listed to each tabpages OnMouseDown event and using coordinates find where the image is. There may be a better/faster way but I don't know of it yet.
 
Hi! I follow your hints and finished it somehow, but when I click button for a new tab, I must use loop to draw for the first to the new tab as you can see in the attached picture, is there anyway to remember available tabs and just only need to add one tab more :confused:
thanks !!!
 

Attachments

  • untitled.JPG
    untitled.JPG
    114 KB · Views: 62
DrawItem event provide the Index for the tab being drawn through the event parameter:
VB.NET:
Dim r As Rectangle = Me.TabControl1.GetTabRect(e.Index)
Why do you AddHandler the DrawItem event, is the TabControl created by code?

You must Dispose the Font and SolidBrush you created.
 
Hi, it runs faster ;)
VB.NET:
Imports System
Imports System.Data
Imports System.Windows.Forms
Imports System.Drawing
Public Class Form1
    Private tabArea As Rectangle
    Private tabTextArea As RectangleF
    Private deleteArea As RectangleF
    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Button1.Location = New Point(TabControl1.TabCount * 100 + TabControl1.Location.X, TabControl1.Location.Y)
        Button1.Height = 28
        Button1.Width = 24
    End Sub
    Private Sub TabControl1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles TabControl1.DrawItem
        Dim drawFont As New Font("Verdana", 7, FontStyle.Italic)
        Dim drawBrush As New SolidBrush(Color.White)
        Dim g As Graphics = e.Graphics
        Dim p As Graphics = e.Graphics
        tabArea = TabControl1.GetTabRect(e.Index)
        deleteArea.Height = 11
        deleteArea.Width = 10
        deleteArea.X = tabArea.X + 5
        deleteArea.Y = tabArea.Y + 2
        g.FillRectangle(Brushes.Blue, deleteArea)
        g.DrawString("X", drawFont, drawBrush, tabArea.X + 5, tabArea.Y + 1)
        drawFont.Dispose()
        drawBrush.Dispose()
    End Sub
    Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        If TabControl1.TabCount < 10 Then
            TabControl1.TabPages.Add(New TabPage)
            TabControl1.SelectTab(TabControl1.TabCount - 1)
        End If
        Button1.Location = New Point(TabControl1.TabCount * TabControl1.ItemSize.Width + TabControl1.Location.X, TabControl1.Location.Y)
        If TabControl1.TabCount * 100 > TabControl1.Width - Button1.Width Then
            Dim divide As Size = New Size(((TabControl1.Width - Button1.Width) / TabControl1.TabCount), TabControl1.ItemSize.Height)
            TabControl1.ItemSize = divide
            Button1.Location = New Point(TabControl1.Width + TabControl1.Location.X - Button1.Width, TabControl1.Location.Y)
        End If
    End Sub
    Function getTabAt(ByVal tc As TabControl, ByVal pt As Point) As TabPage
        For i As Integer = 0 To tc.TabCount - 1
            If tc.GetTabRect(i).Contains(pt) Then
                Return tc.TabPages(i)
            End If
        Next
        Return Nothing
    End Function
    Private Sub TabControl1_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TabControl1.MouseClick
        'If e.Button = Windows.Forms.MouseButtons.Right Then
        'Dim tp As TabPage = getTabAt(sender, e.Location)
        'TabControl1.TabPages.Remove(tp)
        'End If
        If e.Button = Windows.Forms.MouseButtons.Left Then
            Dim tp As TabPage = getTabAt(sender, e.Location)
            tabArea = TabControl1.GetTabRect(TabControl1.TabPages.IndexOf(tp))
            Dim delArea As RectangleF
            delArea.Height = 11
            delArea.Width = 10
            delArea.X = tabArea.X + 5
            delArea.Y = tabArea.Y + 2
            If delArea.Contains(e.Location) And TabControl1.TabCount > 1 Then
                TabControl1.TabPages.Remove(tp)
                If TabControl1.TabCount * 100 > TabControl1.Width - Button1.Width Then
                    Dim divSize As Size = New Size(((TabControl1.Width - Button1.Width) / TabControl1.TabCount), TabControl1.ItemSize.Height)
                    TabControl1.ItemSize = divSize
                Else
                    TabControl1.ItemSize = New Size(100, 25)
                End If
                Button1.Location = New Point(TabControl1.TabCount * TabControl1.ItemSize.Width + TabControl1.Location.X, TabControl1.Location.Y)
            End If
        End If
    End Sub
End Class

now is another one, the backcolor property of tabcontrol isn't exist, it always use "Control" color, I do a search and find some link, temporarily using the second tip in this link Dotnetrix. Mick Doherty's .net tips and tricks.
but when I try to repeat one part of the code above
VB.NET:
Private Sub TabControl1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles TabControl1.DrawItem
My programmable "delete" sign doesn't appear :confused:
Do you know why ???
Thanks !!!
 
DrawItem event is not used when you set style UserPaint.
 
Hi! I made it works :D
edit directly in custom control
VB.NET:
'http://dotnetrix.co.uk/tabcontrol.htm
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Reflection
Imports System.Windows.Forms
Imports System.Drawing

Public Class CustomTabControl
    Inherits System.Windows.Forms.TabControl
#Region " Windows Form Designer generated code "
    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
        setstyle(ControlStyles.AllPaintingInWmPaint Or _
                ControlStyles.DoubleBuffer Or _
                ControlStyles.ResizeRedraw Or _
                ControlStyles.UserPaint, True)

    End Sub

    'UserControl1 overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        components = New System.ComponentModel.Container
    End Sub

#End Region

#Region " InterOP "

    <StructLayout(LayoutKind.Sequential)> _
    Private Structure NMHDR
        Public HWND As Int32
        Public idFrom As Int32
        Public code As Int32
        Public Overloads Function ToString() As String
            Return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}", HWND, idFrom, code)
        End Function
    End Structure

    Private Const TCN_FIRST As Int32 = &HFFFFFFFFFFFFFDDA&
    Private Const TCN_SELCHANGING As Int32 = (TCN_FIRST - 2)

    Private Const WM_USER As Int32 = &H400&
    Private Const WM_NOTIFY As Int32 = &H4E&
    Private Const WM_REFLECT As Int32 = WM_USER + &H1C00&

#End Region

#Region " BackColor Manipulation "

    'As well as exposing the property to the Designer we want it to behave just like any other 
    'controls BackColor property so we need some clever manipulation.
    Private m_Backcolor As Color = Color.Empty
    <Browsable(True), _
    Description("The background color used to display text and graphics in a control.")> _
    Public Overrides Property BackColor() As Color
        Get
            If m_Backcolor.Equals(Color.Empty) Then
                If Parent Is Nothing Then
                    Return Control.DefaultBackColor
                Else
                    Return Parent.BackColor
                End If
            End If
            Return m_Backcolor
        End Get
        Set(ByVal Value As Color)
            If m_Backcolor.Equals(Value) Then Return
            m_Backcolor = Value
            Invalidate()
            'Let the Tabpages know that the backcolor has changed.
            MyBase.OnBackColorChanged(EventArgs.Empty)
        End Set
    End Property
    Public Function ShouldSerializeBackColor() As Boolean
        Return Not m_Backcolor.Equals(Color.Empty)
    End Function
    Public Overrides Sub ResetBackColor()
        m_Backcolor = Color.Empty
        Invalidate()
    End Sub
    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        Dim r As Rectangle = Me.ClientRectangle
        Dim deleteArea As Rectangle = Me.ClientRectangle
        Dim drawFont As New Font("Verdana", 7, FontStyle.Italic)
        Dim drawBrush As New SolidBrush(Color.White)
        For index As Integer = 0 To TabCount - 1
            r = GetTabRect(index)
            Dim bs As ButtonBorderStyle = ButtonBorderStyle.None
            If index = SelectedIndex Then bs = ButtonBorderStyle.Outset
            e.Graphics.FillRectangle(Brushes.White, r)
            ControlPaint.DrawBorder(e.Graphics, r, Color.MediumPurple, bs)
            Dim g As Graphics = e.Graphics
            deleteArea.Height = 11
            deleteArea.Width = 10
            deleteArea.X = r.X + 5
            deleteArea.Y = r.Y + 5
            g.FillRectangle(Brushes.MediumPurple, deleteArea)
            g.DrawString("X", drawFont, drawBrush, r.X + 5, r.Y + 4)
        Next
        drawFont.Dispose()
        drawBrush.Dispose()
    End Sub
#End Region
End Class
Now I can back to my top problem :rolleyes:
 
I almost reach to the target, everything works fine with your code
VB.NET:
  with TabControl1.SelectedTab
    if typeof .Controls(0) is PictureBox then
      dim p as picturebox = DirectCast(.Controls(0), PictureBox)
      p.ImageLocation = ListBox1.SelectedValue
      p.Load
    end if
  end with
just with printing, what I have to do with my own created picturebox in runtime that inherit the printing property (that I use only for pictureBox1) :confused:
VB.NET:
#Region "Printing"
    Private original As Image
    Dim allRect As Rectangle
    Dim cropRect As Rectangle
    Dim startPoint, currentPoint, lastPoint As Point
    Dim topLeftPoint As Point = New Point(15, 15)
    Dim drawSize As Size
    Dim printState As Boolean = True
    Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
       If original IsNot Nothing Then
          startPoint = e.Location
         Timer1.Start()
    End If
    End Sub
    Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
       currentPoint = e.Location
    End Sub
    Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
       Timer1.Stop()
    'End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Dim drawTopLeft As Point
        Dim drawSizeCrop As Size
        If lastPoint <> currentPoint Then
            drawTopLeft.X = Math.Min(startPoint.X, currentPoint.X)
            drawTopLeft.Y = Math.Min(startPoint.Y, currentPoint.Y)
            drawSizeCrop.Width = Math.Abs(startPoint.X - currentPoint.X)
            drawSizeCrop.Height = Math.Abs(startPoint.Y - currentPoint.Y)
            cropRect = New Rectangle(drawTopLeft, drawSizeCrop)
            'PictureBox1.Refresh()
            p.Refresh()
            lastPoint = currentPoint
        End If
        If ((drawSizeCrop.Height > 100) And (drawSizeCrop.Width > 100)) Then
            printState = False
        End If
    End Sub
    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
        e.Graphics.DrawRectangle(Pens.Aquamarine, cropRect)
    End Sub
    Private Sub PrintAll()
        Dim imgRate As Single = original.Height / original.Width
        Dim stanRate As Single = 1.298
        If imgRate > stanRate Then
            drawSize.Height = 1065
            drawSize.Width = CInt(1065 / imgRate)
        ElseIf imgRate < stanRate Then
            drawSize.Height = CInt(820 * imgRate)
            drawSize.Width = 820
        End If
        If ((original.Width <= 820) And (original.Height <= 1065)) Then
            topLeftPoint = New Point((820 - original.Width) / 2 + 15, (1065 - original.Height) / 2 + 15)
            drawSize.Height = original.Height
            drawSize.Width = original.Width
        End If
        allRect = New Rectangle(topLeftPoint, drawSize)
    End Sub
    Private Sub PrintDocument1_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
        If printState Then
            PrintAll()
            e.Graphics.DrawImage(original, allRect)
        Else
            printState = True
            e.Graphics.DrawImage(PictureBox1.Image, 50, 50, cropRect, GraphicsUnit.Pixel)
        End If

    End Sub
    Private Sub PrintPreviewToolStripMenuItem1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles PrintPreviewToolStripMenuItem1.Click
        PrintPreviewDialog1.ShowDialog()
    End Sub
#End Region
this is the code to create my runtime pictureBox, use Handler (where must I put it) ? :eek:
VB.NET:
   Private Sub nextTabBut_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles nextTabBut.Click
        If TabControl1.TabCount < 7 Then
            Dim pn As New Panel()
            Dim pb As New PictureBox()
            TabControl1.TabPages.Add(New TabPage)
            With TabControl1.TabPages(TabControl1.TabCount - 1)
                .BackColor = Color.White
                .Controls.Add(pn) 'adds the panel
                .Padding = New Padding(3, 3, 3, 3)
                pn.Location = New Point(3, 3)
                pn.Padding = New Padding(0, 0, 0, 0)
                pn.Margin = New Padding(3, 3, 3, 3)
                pn.Dock = DockStyle.Fill
                pn.AutoScroll = True
                pn.BackColor = Color.Lavender
                With pn
                    .Controls.Add(pb) 'adds the pictureBox
                    pb.Width = .Width - 17
                    pb.Height = .Height - 17
                    pb.Anchor = AnchorStyles.Top + AnchorStyles.Left
                    pb.SizeMode = PictureBoxSizeMode.Zoom
                    pb.ContextMenuStrip = ContextMenuStrip1
                    pb.BackColor = Color.Lavender
                    'pb.Dock = DockStyle.None
                End With
            End With
        End If
        nextTabBut.Location = New Point(TabControl1.TabCount * TabControl1.ItemSize.Width + TabControl1.Location.X + 5, TabControl1.Location.Y + 2)
        If TabControl1.TabCount * 100 > TabControl1.Width - nextTabBut.Width Then
            Dim divide As Size = New Size(((TabControl1.Width - nextTabBut.Width - 5) / TabControl1.TabCount), TabControl1.ItemSize.Height)
            TabControl1.ItemSize = divide
            nextTabBut.Location = New Point(TabControl1.Width + TabControl1.Location.X - nextTabBut.Width - 2, TabControl1.Location.Y + 3)
        End If
        TabControl1.SelectTab(TabControl1.TabCount - 1)
    End Sub
Thanks so much !!!
 
Back
Top