I know this is a pretty popular question, but none of the solutions I have found have worked for me.
Background: I have a windows forms project in VS2015 that reads data from text files and plots the data as multiple series on a line chart. The Chart.MouseMove event finds the point nearest the mouse and draws a circle around it. The circle is drawn in the Chart_Paint event
Private Sub crtLogView(sender As Object,e As PaintEventArgs) Handles crtLogView.Paint
Dim whitePen as New Pne(Color.White,2)
e.Graphics.DrawEllipse(whitePen,cir) '//cir is a Public Rectangle
End Sub
When moving the mouse across the chart, random controls flicker off then back on which is very annoying. I have posted the MouseMove event code below.
Potential solutions I have tried:
Turning on the DoubleBuffered property of the form, which does nothing
Using the Me.Invalidate() and Me.Update() method, which does not move the circle
Using the Chart.Invalidate() and Chart.Update() method, which works, but is very slow
Adding the following code to my Form_Load routine, which appears to do nothing
Any help with this would be greatly appreciated
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.DoubleBuffer, True)
Me.SetStyle(ControlStyles.UserPaint, True)
MouseMove Event Code:
Private Sub crtLogView_MouseMove(sender As Object, e As MouseEventArgs) Handles crtLogView.MouseMove
'//Show data for closest point to cursor & draw circle around point
Dim hResult As HitTestResult = crtLogView.HitTest(e.X, e.Y)
Dim srsNam As String = ""
Dim mouseY As Single
Dim pntDist As Double = 0
Dim pntX As Single
Dim pntY As Single
Dim mouseX As Single
On Error GoTo ErrTrap
'//Get X-Axis Position as integer
mouseX = Int(hResult.ChartArea.AxisX.PixelPositionToValue(e.X))
'// Set time value
lblTime.Text = String.Format("{0:n2}", hResult.ChartArea.AxisX.PixelPositionToValue(e.X) / 160)
'//Get Y-Axis Position
mouseY = hResult.ChartArea.AxisY.PixelPositionToValue(e.Y)
'//Get distance from mouse to point on Series(0)
pntDist = Math.Abs(crtLogView.Series(0).Points(mouseX).YValues(0) - mouseY)
srsNam = crtLogView.Series(0).Name '//1st series name
'//Find closest series
For i As Integer = 1 To crtLogView.Series.Count - 1
If Math.Abs(crtLogView.Series(i).Points(mouseX).YValues(0) - mouseY) < pntDist Then
pntDist = Math.Abs(crtLogView.Series(i).Points(mouseX).YValues(0) - mouseY)
srsNam = crtLogView.Series(i).Name
End If
Next
'//Set Top/Left values for circle
pntY = crtLogView.ChartAreas(0).AxisY.ValueToPixelPosition(crtLogView.Series(srsNam).Points(mouseX).YValues(0)) - 4
pntX = crtLogView.ChartAreas(0).AxisX.ValueToPixelPosition(Val(mouseX)) - 4
'//Move circle to closest point
cir.Location = New Point(pntX, pntY)
'//Refresh the form to move the circle
'//This works, but takes 2+ seconds to take effect
'crtLogView.Invalidate()
'crtLogView.Update()
'//This does not work
'Me.Invalidate()
'Me.Update()
'//This works, but randomly makes other controls flash/flicker
Me.Refresh()
ErrTrap:
End Sub
In the comments, I offered to provide an example of using a Chart Annotation or a DataPoint Label as an alternative to custom painting a circle around the point under the mouse-cursor and have included that in the code below. However, I realized that a DataPoint Marker should provide the function the OP is seeking and is likely the proper solution. Therefore, that option is also included.
Annotations are chart level graphics where-as the DataPoint Label and DataPoint Marker are as the name implies tied to the individual DataPoints. Proper sizing of annotations can be involved as their size is specified as a percentage of the Chart Area dimensions. This example does not attempt to resize the annotation based on the current chart size.
The following code sample is for a WinForm. In VS, add a new Class to a WinForm project and replace the auto-generated code with this. The set this Form as the startup Form.
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports Charting = System.Windows.Forms.DataVisualization.Charting
Public Class ChartDemo : Inherits Form
Const yMultiplyer As Double = 100.0
Private rnd As Random
Friend WithEvents chart As System.Windows.Forms.DataVisualization.Charting.Chart
Friend WithEvents rbAnnotation As System.Windows.Forms.RadioButton
Friend WithEvents rbDataLabel As System.Windows.Forms.RadioButton
Friend WithEvents rbMarker As System.Windows.Forms.RadioButton
Private lastPoint As Charting.DataPoint
Private ellispeAnnotation As Charting.EllipseAnnotation
Public Sub New()
InitializeComponent()
rnd = New Random(0) ' use same basis for each run
SetupChart()
End Sub
Private Sub InitializeComponent()
Me.chart = New System.Windows.Forms.DataVisualization.Charting.Chart()
Me.rbAnnotation = New System.Windows.Forms.RadioButton()
Me.rbDataLabel = New System.Windows.Forms.RadioButton()
Me.rbMarker = New System.Windows.Forms.RadioButton()
CType(Me.chart, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
Me.chart.Anchor = AnchorStyles.Top Or
AnchorStyles.Bottom Or
AnchorStyles.Left Or
AnchorStyles.Right
Me.chart.Location = New Point(4, 50)
Me.chart.Size = New Size(600, 500)
Me.rbAnnotation.AutoSize = True
Me.rbAnnotation.Location = New Point(50, 10)
Me.rbAnnotation.TabIndex = 1
Me.rbAnnotation.Text = "Use Annotation"
Me.rbAnnotation.UseVisualStyleBackColor = True
Me.rbDataLabel.AutoSize = True
Me.rbDataLabel.Location = New Point(200, 10)
Me.rbDataLabel.TabIndex = 2
Me.rbDataLabel.Text = "Use Data Label"
Me.rbDataLabel.UseVisualStyleBackColor = True
Me.rbMarker.AutoSize = True
Me.rbMarker.Location = New Point(400, 10)
Me.rbMarker.TabIndex = 3
Me.rbMarker.Text = "Use Data Marker"
Me.rbMarker.UseVisualStyleBackColor = True
Me.rbMarker.Checked = True
Me.AutoScaleDimensions = New SizeF(96.0!, 96.0!)
Me.AutoScaleMode = AutoScaleMode.Dpi
Me.ClientSize = New Size(610, 555)
Me.Controls.AddRange({Me.rbDataLabel, Me.rbAnnotation, Me.rbMarker, Me.chart})
Me.Text = "Charting Demo"
CType(Me.chart, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub
Private Sub SetupChart()
chart.ChartAreas.Clear()
chart.Legends.Clear()
chart.Series.Clear()
chart.Annotations.Clear()
Dim area1 As New Charting.ChartArea("Area1")
chart.ChartAreas.Add(area1)
Dim ser As Charting.Series = chart.Series.Add("Series1")
ser.ChartArea = area1.Name
ser.ChartType = Charting.SeriesChartType.Line
' define defaults for point DataLabels
ser.LabelBorderColor = Color.Red
ser.LabelBorderWidth = 1
ser.LabelBackColor = Color.WhiteSmoke
ser.LabelForeColor = Color.Black
' define defaults for point DataMarkers
ser.MarkerSize = 10
ser.MarkerBorderWidth = 3
ser.MarkerBorderColor = Color.Red
ser.MarkerColor = Color.Transparent
' points for demo chart
For x As Double = -5.0 To 5.0
ser.Points.AddXY(x, rnd.NextDouble * yMultiplyer)
Next
ellispeAnnotation = CreateEllipseAnnotation()
ellispeAnnotation.Visible = False
chart.Annotations.Add(ellispeAnnotation)
End Sub
Private Sub chart_MouseLeave(sender As Object, e As EventArgs) Handles chart.MouseLeave
ellispeAnnotation.Visible = False
ClearLastPointDataLabel()
ClearLastPointMarker()
End Sub
Private Function CreateEllipseAnnotation() As Charting.EllipseAnnotation
Dim ret As New Charting.EllipseAnnotation()
ret.ForeColor = Color.Black
ret.Font = New Font("Arial", 10)
ret.LineWidth = 2
ret.Height = 7.5 ' % ChartArea height
ret.Width = 15 ' % ChartArea width
ret.BackColor = Color.PaleGoldenrod
ret.LineDashStyle = Charting.ChartDashStyle.Solid
Return ret
End Function
Private Sub chart_MouseMove(sender As Object, e As MouseEventArgs) Handles chart.MouseMove
Dim htr As Charting.HitTestResult = chart.HitTest(e.X, e.Y)
If htr.ChartElementType = Charting.ChartElementType.DataPoint Then
Dim pt As Charting.DataPoint = DirectCast(htr.Object, Charting.DataPoint)
If pt IsNot lastPoint Then
SetDataPointLabel(pt)
SetDataPointAnnotation(pt)
SetDataPointMarker(pt)
lastPoint = pt
End If
End If
End Sub
Private Sub SetDataPointAnnotation(pt As Charting.DataPoint)
If rbAnnotation.Checked Then
ellispeAnnotation.AnchorDataPoint = pt
ellispeAnnotation.Text = String.Format("{0:N2}, {1:N2}", pt.XValue, pt.YValues(0))
ellispeAnnotation.Visible = True
End If
End Sub
Private Sub SetDataPointLabel(pt As Charting.DataPoint)
ClearLastPointDataLabel()
If rbDataLabel.Checked Then
pt.Label = "#VALX{N2}, #VALY{N2}" ' case sensative, use uppercase for #VALX, #VALY
pt.IsValueShownAsLabel = True
End If
End Sub
Private Sub ClearLastPointDataLabel()
If lastPoint IsNot Nothing Then
lastPoint.Label = String.Empty
lastPoint.IsValueShownAsLabel = False
End If
End Sub
Private Sub SetDataPointMarker(pt As Charting.DataPoint)
ClearLastPointMarker()
If rbMarker.Checked Then pt.MarkerStyle = Charting.MarkerStyle.Circle
End Sub
Private Sub ClearLastPointMarker()
If lastPoint IsNot Nothing Then
lastPoint.MarkerStyle = Charting.MarkerStyle.None
End If
End Sub
Private Sub rbAnnotation_CheckedChanged(sender As Object, e As EventArgs) Handles rbAnnotation.CheckedChanged
If Not rbAnnotation.Checked Then
ellispeAnnotation.Visible = False
End If
End Sub
Private Sub rbDataLabel_CheckedChanged(sender As Object, e As EventArgs) Handles rbDataLabel.CheckedChanged
ClearLastPointDataLabel()
End Sub
Private Sub rbMarker_CheckedChanged(sender As Object, e As EventArgs) Handles rbMarker.CheckedChanged
ClearLastPointMarker()
End Sub
End Class
Related
I would like to print a chart in my vb.net application but when I print it, it's very small and I can't find how to resize it.
For now, I'm using this code: (Found here : Some msdn printing subject)
Private Sub BT_Print_Click(sender As Object, e As EventArgs) Handles BT_Print.Click
Dim pdS As New PrintDocument()
AddHandler pdS.PrintPage, AddressOf pds_PrintPage
pdS.DefaultPageSettings.Landscape = True
Dim PrintDialog1 As New PrintPreviewDialog
PrintDialog1.Document = pdS
If (PrintDialog1.ShowDialog = DialogResult.OK) Then
Chart_Requis.Printing.PrintDocument.DefaultPageSettings.Landscape = True
pdS.Print()
End If
End Sub
Private Sub pds_PrintPage(sender As Object, ev As PrintPageEventArgs)
Dim chartPosition As New Rectangle(1, 1, ev.MarginBounds.Width, ev.MarginBounds.Height)
Chart_Requis.Printing.PrintPaint(ev.Graphics, chartPosition)
End Sub
After all my attempt I just reached to move margins... which is good but I still cannot read my chart correctly...
Do someone had the same issue and found a solution ?
----------------------------------------EDIT-------------------------------------
As I explain it the comment of your answer, I now can change chart's position and size but blank squares appears and I don't know why !
By image you have posted seems your issue is ChartArea not Chart Control.
You need to increase Area of your Chart Area.
In the example below (which can help you in that) I have used two parameters scaleX and scaleY .
You can work on those two to find the right size becomes on print.
Note that.: As Chart Area in the example I’ve used the first of your Chart Control, but, you can use another calling it by name. Hope is what you needs.
Private Sub BT_Print_Click(sender As Object, e As EventArgs) Handles BT_Print.Click
Using pdS As New PrintDocument()
Dim scaleX As Single = 1.5
Dim scaleY As Single = 1.2
With pdS.DefaultPageSettings
.Landscape = True
.Margins = New Margins(10, 10, 10, 10)
End With
Chart_Requis.Printing.PrintDocument = pdS
AddHandler pdS.PrintPage, Sub(obj As Object, ev As PrintPageEventArgs)
Using ev.Graphics
With Chart_Requis.ChartAreas.FirstOrDefault
Dim initialP As DataVisualization.Charting.ElementPosition = .Position
Dim newP As Rectangle = New Rectangle With {
.X = CInt(initialP.X),
.Y = CInt(initialP.Y),
.Width = CInt(initialP.Width * scaleX),
.Height = CInt(initialP.Height * scaleY)
}
.Position.FromRectangleF(newP)
Chart_Requis.Printing.PrintPaint(ev.Graphics, ev.MarginBounds)
.Position = initialP
End With
End Using
End Sub
Using PrintDialog1 As New PrintPreviewDialog With {
.Document = pdS
}
If (PrintDialog1.ShowDialog = DialogResult.OK) Then
pdS.Print()
End If
End Using
End Using
End Sub
Ok, I finally found something which worked for me here and I adapt it to my case : Another Stackoverflow subject
I let you see the final chart here :
I add a dialogbox to choose the printer and adapt a bit the original code as you can see here :
Private Sub BT_Print_Click(sender As Object, e As EventArgs) Handles BT_Print.Click
Print_Preview()
End Sub
Public Sub Print_Preview()
Dim Print_Doc As PrintDocument
Print_Doc = Chart_Requis.Printing.PrintDocument
AddHandler Print_Doc.PrintPage, New PrintPageEventHandler(AddressOf Print_Page)
'SHEET PARAMETERS
Print_Doc.DefaultPageSettings.Landscape = True
Print_Doc.DefaultPageSettings.Margins = New Printing.Margins(0, 1, 1, 1)
Print_Doc.DefaultPageSettings.Color = True
'CHOOSING PRINTER AND OTHER PARAMETERS
Dim Print_dlg As New PrintDialog
Print_dlg.Document = Print_Doc
Dim result As DialogResult = Print_dlg.ShowDialog()
'IF DIALOGBOX OK THEN CONTINUE OTHERWISE NO
If (result = DialogResult.OK) Then
'PUT MARKERS TO THE RIGHT SIZE
For i = 0 To Chart_Requis.Series.Count - 1
Chart_Requis.Series(i).MarkerSize = 1
Next
'CHECKING BEFORE PRINTING
Chart_Requis.Printing.PrintPreview()
End If
End Sub
Private Sub Print_Page(ByVal sender As Object, ByVal ev As PrintPageEventArgs)
'DEFINI LA POSITION ET LA TAILLE DU GRAPHIQUE
Dim chartPosition As New Rectangle(-70, -50, 2500, 2300)
Chart_Requis.Printing.PrintPaint(ev.Graphics, chartPosition)
End Sub
Hope it will help someone in the same trouble than me today...
Greetings,
I want To create a graphic representation of a seating chart (could be in a wedding venue or in cars...). I used a splitted panel, in the splittedPanel1 i put the buttons to create the cars, and inside the splitted panel2 i want to put the graphic representation. Inside SplittedPanel2, I've created a panel and a pictureBox (to represent some fixed areas in the real world).
I've also created a class called SimpleCar. SimpleCar is composed of 5 TextBox (es) and a PictureBox all in a panel to represent a car: the textBoses represent the passengers names and the car label, and the pictureBox to put an image representing a car (or a table). I've also made a sub to Add dynamically a SimpleCar.
2 problems occur when i want to move this new panel (dynamically created), using MouseDown and MouseUp events:
- first pb: while moving the existing panel, the screen flashes and the movement is not smooth
- second pb: i can't move a panel dynamically created
Note that moving a PictureBox by this code is very smooth but moving a panel is not user friendly.
I expect moving a dynamically created a panel smoothly, or should I reconsider displaying the cars in another way than in a panel?
Knowing that the final purpose of the code is to export a picture of all the created tables in the venue. I also tested the code with a groupBox and the results aren't good.
The simpleCar class is described in the code below:
Class SimpleCar
Public carNameBox, passengerNameBox1, passengerNameBox2,
passengerNameBox3, passengerNameBox4 As TextBox
Public carPictureBox As PictureBox
Public carGroup As Panel
Public Sub New()
carGroup = New Panel
carNameBox = New TextBox With {.Text = "carNmBx",
.BackColor = Color.Yellow,
.Name = "carNmBx"}
passengerNameBox1 = New TextBox With {.Text = "txtPassNmBx1",
.BackColor = Color.BlanchedAlmond,
.Name = "TextBox1"}
passengerNameBox2 = New TextBox With {.Text = "txtPassNmBx2",
.BackColor = Color.AliceBlue,
.Name = "TextBox2"}
passengerNameBox3 = New TextBox With {.Text = "txtPassNmBx3",
.BackColor = Color.Azure,
.Name = "TextBox3"}
passengerNameBox4 = New TextBox With {.Text = "txtPassNmBx4",
.BackColor = Color.Cyan,
.Name = "TextBox4"}
carPictureBox = New PictureBox With {.Text = "picBx1",
.BackColor = Color.BlanchedAlmond,
.Name = "picBox1"}
Dim fdialog As New OpenFileDialog()
fdialog.FileName = String.Empty
fdialog.Multiselect = True
If fdialog.ShowDialog = DialogResult.OK Then
If fdialog.FileNames.Length = 2 Then
carPictureBox.Image = Image.FromFile(fdialog.FileNames(0))
ElseIf fdialog.FileNames.Length = 1 Then
carPictureBox.Image = Image.FromFile(fdialog.FileName)
End If
End If
carGroup.Controls.Add(carPictureBox)
carGroup.Controls.Add(carNameBox)
carGroup.Controls.Add(passengerNameBox1)
carGroup.Controls.Add(passengerNameBox2)
carGroup.Controls.Add(passengerNameBox3)
carGroup.Controls.Add(passengerNameBox4)
End Sub
End Class
To Add dynamically a SimpleCar in the code below:
Public Sub Add_car()
Dim carType As SimpleCar
carType = New SimpleCar
Dim carPs1 = carType.passengerNameBox1
Dim carPs2 = carType.passengerNameBox2
Dim carPs3 = carType.passengerNameBox3
Dim carPs4 = carType.passengerNameBox4
Dim carNm = carType.carNameBox
Dim carPic = carType.carPictureBox
Dim carGroupBox = carType.carGroup
SplitContainer1.Panel2.Controls.Add(carGroupBox)
End Sub
So the problem occurs when i use this code to move a panel (if you replace PictureBox by Panel, even GroupBox) (it worked fine when I wanted to move one control: PictureBox1 in this sample):
'Drag To move PictureBox1 along with mouse-------------------------------------------
Dim oldX As Short
Dim oldY As Short
Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles PictureBox1.MouseDown
Dim X As Single = e.X
Dim Y As Single = e.Y
PictureBox1.Cursor = Cursors.SizeAll
oldX = CShort(X)
oldY = CShort(Y)
End Sub
Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As MouseEventArgs) Handles PictureBox1.MouseUp
Dim X As Single = e.X
Dim Y As Single = e.Y
PictureBox1.Cursor = Cursors.Default
End Sub
' to limit the movement within the app----------------------------------
Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles PictureBox1.MouseMove
If e.Button = MouseButtons.Left Then
Dim ProposedLocation As New Point(PictureBox1.Left - (oldX - e.X), PictureBox1.Top - (oldY - e.Y))
PictureBox1.Left = CInt(IIf(ProposedLocation.X < 0, 0, IIf(ProposedLocation.X > SplitContainer1.Panel2.Width - PictureBox1.Width, SplitContainer1.Panel2.Width - PictureBox1.Width, ProposedLocation.X)))
PictureBox1.Top = CInt(IIf(ProposedLocation.Y < 0, 0, IIf(ProposedLocation.Y > SplitContainer1.Panel2.Height - PictureBox1.Height, SplitContainer1.Panel2.Height - PictureBox1.Height, ProposedLocation.Y)))
End If
End Sub
I'm creating a board game for a piece of coursework. For the board, I'm using some nested For loops running through a 2D array to generate a "Space" object at each square.
The Space object contains a picturebox and some data about that space.
How can I handle events caused by clicking on the generated picturebox without having to hard-code it for each space?
I noticed this question seems to address this, but it's in C# and I couldn't translate it to VB.Net.
Edit:
This is how the board is generated
Dim board(23, 24) As Space
Private Sub GenerateBoard()
Dim spaceSize As New Size(30, 30)
Dim spaceLocation As New Point
Dim validity As Boolean
For Y = 0 To 24
For X = 0 To 23
spaceLocation.X = 6 + (31 * X)
spaceLocation.Y = 6 + (31 * Y)
If validSpaces(Y).Contains(X + 1) Then
validity = True
Else
validity = False
End If
board(X, Y) = New Space(validity, spaceSize, spaceLocation)
Me.Controls.Add(board(X, Y).imageBox)
board(X, Y).imageBox.BackColor = Color.Transparent
board(X, Y).imageBox.BringToFront()
Next
Next
End Sub
Space Class:
Public Class Space
Dim _active As Boolean
Dim _imageBox As PictureBox
Public Sub New(ByVal activeInput As Boolean, ByVal size As Size, ByVal location As Point)
_active = activeInput
_imageBox = New PictureBox
With _imageBox
.Size = size
.Location = location
.Visible = False
End With
End Sub
Property active As Boolean
Get
Return _active
End Get
Set(value As Boolean)
_active = value
End Set
End Property
Property imageBox As PictureBox
Get
Return _imageBox
End Get
Set(value As PictureBox)
_imageBox = value
End Set
End Property
Public Sub highlight()
With _imageBox
.Image = My.Resources.Highlighted_slab
.Visible = True
End With
End Sub
End Class
First all controls created by designer(textbox, label...) a generated by code too, but VisualStudio write this for you. If you open Designer file(yourForm.Designer.vb), then you can see all code how to generate a controls.
If you want a create event handler for your pictureBox , then:
//Initialize control
Private WithEvents _imageBox as PictureBox
Then create a event handler method:
Private Sub imageBox_Click(sender as Object, e as EventArgs)
//Your code
End Sub
Then in VB.NET you can assign a Event handler to the Event in two ways
first: In class constructor after you created a pictureBox( New PictureBox()) add
AddHandler Me._imageBox, AddressOf Me.imageBox_Click
second: On line we you created a event handler add next:
Private Sub imageBox_Click(sender as Object, e as EventArgs) Handles _imageBox.Click
//Your code
End Sub
And remember add your pictureBox to form controls YourForm.Controls.Add(spaceInstance.ImageBox)
Is it possible to use an image as the checkbox "checked" indicator square?
I know I can use a background image, but that goes behind the label aswell and also it is not possible (as far as I know) to align it.
How can I use an image instead of the square and leave the label and all other customization as they are?
Thanks in advance!
You look like this?
Dim frm As New Form
frm.Size = New Size(320, 200)
Dim iList As New ImageList
iList.Images.Add(Image.FromFile("check.png"), Color.White)
iList.Images.Add(Image.FromFile("uncheck.png"), Color.White)
Dim chk As New CheckBox
chk.Text = "Check Box With Image"
chk.AutoSize = False
chk.Size = New Size(350, 20)
chk.ImageList = iList
chk.ImageIndex = 1
chk.CheckAlign = ContentAlignment.MiddleRight
chk.ImageAlign = ContentAlignment.MiddleLeft
chk.TextImageRelation = TextImageRelation.ImageBeforeText
chk.Location = New Point(32, 32)
frm.Controls.Add(chk)
AddHandler chk.CheckStateChanged,
Sub(sender1 As Object, e1 As EventArgs)
chk.ImageIndex = IIf(chk.Checked, 0, 1)
End Sub
frm.ShowDialog()
UPDATE #1: Actually, #brahm solution's below is much better than mine!
UPDATE #2: Actually, it's not. Now I see how he did it: he's moving the checkbox out of sight by placing it way off the visible Form's area. Not a great solution...
The ideal solution would be to subclass the CheckBox control and do your own rendering by overriding the OnPaint method.
An easier, although probably messier solution, would be to place a PictureBox over the check box and control the image in the PictureBox through the CheckBox's CheckedChange event.
Another option:
You could still use the CheckBox in button mode (Appearance = Button), as you suggested, but then add a label right next to it.
Then, handle the Click event on the Label to toggle the Checked state of the CheckBox. Then end result should provide you exactly what you are looking for.
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Windows.Forms.VisualStyles
Public Class ImageCheckBox
Public State As CheckBoxState = CheckBoxState.UncheckedNormal
Public Hot As Boolean = False
Public Pressed As Boolean = False
Public ImageDictionary As Dictionary(Of CheckBoxState, Image) = New Dictionary(Of CheckBoxState, Image)
Private Const PaddingModifier As Integer = 2
Sub New()
Me.New(New Dictionary(Of CheckBoxState, Image) From {
{CheckBoxState.CheckedDisabled, My.Resources.form_checkbox_checked},
{CheckBoxState.CheckedHot, My.Resources.form_checkbox_checked},
{CheckBoxState.CheckedNormal, My.Resources.form_checkbox_checked},
{CheckBoxState.CheckedPressed, My.Resources.form_checkbox_checked},
{CheckBoxState.UncheckedDisabled, My.Resources.form_checkbox_unchecked},
{CheckBoxState.UncheckedHot, My.Resources.form_checkbox_unchecked},
{CheckBoxState.UncheckedNormal, My.Resources.form_checkbox_unchecked},
{CheckBoxState.UncheckedPressed, My.Resources.form_checkbox_unchecked}})
End Sub
Sub New(imageDictionary As Dictionary(Of CheckBoxState, Image))
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.ImageDictionary = imageDictionary
End Sub
Sub CheckBox_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
'Return if the specific Image is not found
If Not ImageDictionary.ContainsKey(State) Then Return
'Get the Size of the CheckBox
Dim glyphSize As Size = CheckBoxRenderer.GetGlyphSize(e.Graphics, State)
'Get the Location of the CheckBox in relation to the Alignment of it
Dim glyphLocation As Point
Select Case Me.CheckAlign
Case Drawing.ContentAlignment.TopLeft
glyphLocation = New Point(Me.Padding.Left, Me.Padding.Top)
Exit Select
Case Drawing.ContentAlignment.TopCenter
glyphLocation = New Point(Me.Padding.Left + (Me.Width - glyphSize.Width) / 2, Me.Padding.Top)
Exit Select
Case Drawing.ContentAlignment.TopRight
glyphLocation = New Point(Me.Padding.Left + Me.Width - glyphSize.Width, Me.Padding.Top)
Exit Select
Case Drawing.ContentAlignment.MiddleLeft
glyphLocation = New Point(Me.Padding.Left, Me.Padding.Top + (Me.Height - glyphSize.Height) / 2)
Exit Select
Case Drawing.ContentAlignment.MiddleCenter
glyphLocation = New Point(Me.Padding.Left + (Me.Width - glyphSize.Width) / 2, Me.Padding.Top + (Me.Height - glyphSize.Height) / 2)
Exit Select
Case Drawing.ContentAlignment.MiddleRight
glyphLocation = New Point(Me.Padding.Left + Me.Width - glyphSize.Width, Me.Padding.Top + (Me.Height - glyphSize.Height) / 2)
Exit Select
Case Drawing.ContentAlignment.BottomLeft
glyphLocation = New Point(Me.Padding.Left, Me.Padding.Top + Me.Height - glyphSize.Height)
Exit Select
Case Drawing.ContentAlignment.BottomCenter
glyphLocation = New Point(Me.Padding.Left + (Me.Width - glyphSize.Width) / 2, Me.Padding.Top + Me.Height - glyphSize.Height)
Exit Select
Case Drawing.ContentAlignment.BottomRight
glyphLocation = New Point(Me.Padding.Left + Me.Width - glyphSize.Width, Me.Padding.Top + Me.Height - glyphSize.Height)
Exit Select
End Select
'Set the drawing Area
Dim glyphRectangle As Rectangle = New Rectangle(glyphLocation, glyphSize)
'Enlarge the Rectangle to completely hide default symbol
Dim clearRectangle As Rectangle = New Rectangle(glyphLocation.X - PaddingModifier,
glyphLocation.Y - PaddingModifier,
glyphSize.Width + 2 * PaddingModifier,
glyphSize.Height + 2 * PaddingModifier)
'Draw the Parent Background over the default CheckBox to clear it
CheckBoxRenderer.DrawParentBackground(e.Graphics, clearRectangle, Me)
Debug.WriteLine(State)
'Finally draw the custom CheckBox image on the position of the default one
e.Graphics.DrawImage(ImageDictionary(State), glyphRectangle)
End Sub
Sub CheckBox_MouseClick(sender As Object, e As EventArgs) Handles Me.MouseClick
Me.Checked = Not Me.Checked
End Sub
Sub CheckBox_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
Me.Pressed = True
End Sub
Sub CheckBox_MouseUp(sender As Object, e As MouseEventArgs) Handles Me.MouseUp
Me.Pressed = False
End Sub
Sub CheckBox_MouseEnter(sender As Object, e As EventArgs) Handles Me.MouseEnter
Me.Hot = True
End Sub
Sub CheckBox_MouseLeave(sender As Object, e As EventArgs) Handles Me.MouseLeave
Me.Hot = False
End Sub
Public Sub updateState() Handles Me.MouseClick, Me.MouseDown, Me.MouseUp, Me.MouseEnter, Me.MouseLeave, Me.EnabledChanged
Debug.WriteLine(Me.Checked & " " & Me.Enabled & " " & Me.Hot & " " & Me.Pressed)
Me.State = CurrentState()
Me.Refresh()
Debug.WriteLine(State)
End Sub
Public Function CurrentState() As CheckBoxState
If (Me.Checked) Then
If (Not Me.Enabled) Then Return CheckBoxState.CheckedDisabled
If (Me.Pressed) Then Return CheckBoxState.CheckedPressed
If (Me.Hot) Then Return CheckBoxState.CheckedHot
Return CheckBoxState.CheckedNormal
Else
If (Not Me.Enabled) Then Return CheckBoxState.UncheckedDisabled
If (Me.Pressed) Then Return CheckBoxState.UncheckedPressed
If (Me.Hot) Then Return CheckBoxState.UncheckedHot
Return CheckBoxState.UncheckedNormal
End If
End Function
End Class
I also had this problem with a mute/unmute audiotrack i first went for the CheckBox but deceided to just use the PictureBox click event and used .location to get the New Point overlay the other PictureBox and enable the visibility of the one or the other box, works fine for a complete newb that i am :-)
Picture of the PictureBox in the Designer
Private Sub PictureBoxMute_Click(sender As Object, e As EventArgs) Handles PictureBoxMute.Click
PictureBoxMute.Visible = False
PictureBoxUnmute.Location = New Point(590, 433)
PictureBoxUnmute.Visible = True
Volume = 0
myplayer.Volume = Volume
End Sub
Private Sub PictureBoxUnmute_Click(sender As Object, e As EventArgs) Handles PictureBoxUnmute.Click
PictureBoxUnmute.Visible = False
PictureBoxMute.Visible = True
Volume = 1
myplayer.Volume = Volume
End Sub
I'm using the attached code to add another line\row of controls beneath an existing set (when a label is clicked). There could be quite a few rows added so I'm having to repeat the code many times using the counter (i) to keep track...
Is there a better method for doing this?
Private Sub Label10_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles LblExpandSearch.Click
If i = 0 Then
'TextBox7
'
Dim TextBox7 As New TextBox
TextBox7.Size = New Size(302, 20)
TextBox7.Name = "TextBox7"
TextBox7.Location = New System.Drawing.Point(60, 135)
Me.ExpAdvancedSearch.Controls.Add(TextBox7)
'RadioButton5
'
Dim RadioButton5 As New RadioButton
RadioButton5.AutoSize = True
RadioButton5.Checked = True
RadioButton5.Location = New System.Drawing.Point(77, 112)
RadioButton5.Name = "RadioButton5"
RadioButton5.Size = New System.Drawing.Size(55, 17)
RadioButton5.TabIndex = 48
RadioButton5.TabStop = True
RadioButton5.Text = "NEAR"
RadioButton5.UseVisualStyleBackColor = True
ElseIf i = 1 Then
ExpAdvancedSearch.Size_ExpandedHeight = 260
'TextBox8
'
Dim TextBox8 As New TextBox
TextBox8.Size = New Size(302, 20)
TextBox8.Name = "TextBox8"
TextBox8.Location = New System.Drawing.Point(60, 185)
Me.ExpAdvancedSearch.Controls.Add(TextBox8)
'RadioButton9
'
Dim RadioButton9 As New RadioButton
RadioButton9.AutoSize = True
RadioButton9.Checked = True
RadioButton9.Location = New System.Drawing.Point(77, 162)
RadioButton9.Name = "RadioButton9"
RadioButton9.Size = New System.Drawing.Size(55, 17)
RadioButton9.TabIndex = 48
RadioButton9.TabStop = True
RadioButton9.Text = "NEAR"
RadioButton9.UseVisualStyleBackColor = True
End If
i = i + 1
End Sub
Hmmm.. UseVisualStyleBackColor says 'winforms' to me.
A few points...
Don't add controls all to one panel, use a usercontrol.
Then just add instances of that.
Don't process click events from a label
Use a linklabel or button. Anything else = being mean to users. Of course it makes sense to you, you thought of it! Now so with users, this is black and white.
Sample...
Very minimal of course. You'll want to:
Put the items in a scrollable panel instead of right on the form.
Add them to a generic list of uc probably, too.
Set form's min/max size - to allow reasonable sizing (allow any height > ~100)
Set uc's and controls .Anchor properties to allow reasonable resizing
uc.vb
Public Class uc
Inherits System.Windows.Forms.UserControl
Private components As System.ComponentModel.IContainer
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
Friend WithEvents LinkLabel1 As System.Windows.Forms.LinkLabel
Public Sub New()
MyBase.New()
Me.TextBox1 = New System.Windows.Forms.TextBox
Me.LinkLabel1 = New System.Windows.Forms.LinkLabel
Me.SuspendLayout()
Me.TextBox1.Location = New System.Drawing.Point(8, 8)
Me.TextBox1.Name = "TextBox1"
Me.TextBox1.Size = New System.Drawing.Size(88, 20)
Me.TextBox1.TabIndex = 0
Me.TextBox1.Text = "TextBox1"
Me.LinkLabel1.Enabled = False
Me.LinkLabel1.Location = New System.Drawing.Point(112, 8)
Me.LinkLabel1.Name = "LinkLabel1"
Me.LinkLabel1.Size = New System.Drawing.Size(24, 16)
Me.LinkLabel1.TabIndex = 1
Me.LinkLabel1.TabStop = True
Me.LinkLabel1.Text = "add"
Me.Controls.Add(Me.LinkLabel1)
Me.Controls.Add(Me.TextBox1)
Me.Name = "uc"
Me.Size = New System.Drawing.Size(148, 36)
Me.ResumeLayout(False)
End Sub
Private _addcallback As EventHandler = Nothing
Public Property AddCallback() As EventHandler
Get
Return _addcallback
End Get
Set(ByVal Value As EventHandler)
_addcallback = Value
LinkLabel1.Enabled = Not Value Is Nothing
End Set
End Property
Private Sub LinkLabel1_LinkClicked(ByVal sender As System.Object, ByVal e As System.Windows.Forms.LinkLabelLinkClickedEventArgs) Handles LinkLabel1.LinkClicked
If AddCallback Is Nothing Then Throw New ApplicationException("AddCallback not set on a uc") ' ALWAYS check for errors like this
_addcallback(Me, Nothing)
AddCallback = Nothing ' gray myself out, can't insert in thie implementation
End Sub
End Class
frm.vb
Public Class frm
Inherits System.Windows.Forms.Form
Private components As System.ComponentModel.IContainer
Public Sub New()
MyBase.New()
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 266)
Me.Name = "Form1"
Me.Text = "Form1"
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
AddClicked(Me, Nothing)
End Sub
Private Sub AddClicked(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myuc As New uc
myuc.AddCallback = AddressOf AddClicked
If Controls.Count > 0 Then
myuc.Top = Controls(Controls.Count - 1).Bottom
End If
Me.Controls.Add(myuc)
End Sub
End Class
I don't know if there's a "less code" approach to this but I do know that you can save your fingers using a With statement.
Dim RadioButton5 As New RadioButton
With RadioButton5
.AutoSize = True
.Checked = True
.Location = New System.Drawing.Point(77, 112)
.Name = "RadioButton5"
.Size = New System.Drawing.Size(55, 17)
.TabIndex = 48
.TabStop = True
.Text = "NEAR"
.UseVisualStyleBackColor = True
End With
If you need to add an indefinite number of items to a single page, then you need to store those items in an array list that we can later add to the page dynamically.
Imports System.Collections.Generic
Partial Class Default2
Inherits System.Web.UI.Page
''# the i integer is here for helping to set the ID of the radio button
''# as well as the tabindex
Private Shared _i As Integer
Public Shared Property i As Integer
Get
Return _i
End Get
Set(ByVal value As Integer)
_i = value
End Set
End Property
''# we need to create an array of our control list class
Public Shared _ctrlList As List(Of ControlList)
''# page load event
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
''# if the page is not a postback, then we need to initialize the Control List
_ctrlList = New List(Of ControlList)
i = 0
End If
End Sub
''# button click event
Protected Sub button_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button.Click
''# create a new RadioButton every time the button is clicked
Dim rb As RadioButton = New RadioButton
With rb
.ID = "radioButton" + i.ToString
.Checked = True
.TabIndex = 48 + i
.Text = "NEAR"
End With
''# create a new literal every time the button is clicked
Dim lt As Literal = New Literal
With lt
.ID = "literal" + i.ToString
.Text = " <strong>my fancy text</strong><br />"
End With
''# add the radio button and literal to our custom array
_ctrlList.Add(New ControlList(rb, lt))
''# loop through the array and add the controls to the page
For Each cl In _ctrlList
LabelPlaceHolder.Controls.Add(cl.RadioBtn)
LabelPlaceHolder.Controls.Add(cl.Litrl)
Next
''# increment the i counter so that we have unique radioButton ID's
i = i + 1
End Sub
''# this is our custom Control List
''# the idea behind this is for us to store
''# an array of Radio Buttons and literals to
''# spit out onto the page
''# NOTE: you can add as many controls as you like
''# to this list and even add static "Literals" to
''# help you with your formatting (IE: DIV tags or <BR> tags
Public Class ControlList
Private _RadioBtn As RadioButton
Public Property RadioBtn As RadioButton
Get
Return _RadioBtn
End Get
Set(ByVal value As RadioButton)
_RadioBtn = value
End Set
End Property
Private _Litrl As Literal
Public Property Litrl As Literal
Get
Return _Litrl
End Get
Set(ByVal value As Literal)
_Litrl = value
End Set
End Property
Public Sub New(ByVal radioBtn As RadioButton, ByVal litrl As Literal)
_RadioBtn = radioBtn
_Litrl = litrl
End Sub
End Class
End Class
Try this and see how it works. All you need in your ASPX is
<form id="form1" runat="server">
<asp:PlaceHolder runat="server" id="LabelPlaceHolder" /><br />
<asp:Button ID="button" runat="server" Text="click me" />
</form>
Basically what this does is add an additional control set to the page every time the button is clicked. You can have an indefinite number of controls on the page without adding any additional code.