I can't seem to find a working solution.
I have a custom datagridview cell based on the DataGridViewTextboxColumn. I'm using VB 2005 for this.
I'm trying to implement a design-time property I can use to store a SQL statement to be used by the cells in the column. The lookup is the same for each row but the returned value is different.
I have a version which requires I set the statement during the CellFormatting and RowsAdded events , but I want to make it so all I have to do is provide a SQL statement (i.e. Select JobName From JobList where JobNo = {0}) as a property in the column class. I want to bind the cell and use the the value it gets as the replacement value for the statement. I can not get the SQL statement property to persist (right word?)
Part of my problem is that I'm not sure if the property should go in the custom COLUMN or the custom CELL definition. I've been trying to add the property to the column, which seems logical since I want the same value to be used for each instance if the cell without having to set the statement value each time the cell format event is fired.
I found and tried these attributes:
<Browsable(True), _
EditorBrowsable(EditorBrowsableState.Always), _
Category("Data"), _
Description("The SQL Query to use for lookup. Make sure it will work with string.format"), _
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)> _
Public Property ScalarSQLStatement() As String
End Property
It doesn't work. I can enter the values in the column editor, but if I exit the editor and go back in the property is reset to it's default value.
EDIT ----
I have created subclass for the DataGridTextboxCell and put this in it:
Imports System.ComponentModel
Public Class DataGridViewScalarValue2TextboxCell
Inherits DataGridViewTextBoxCell
Private _scalarStatement As String = String.Empty
Private _ReturnValue As String = String.Empty
Private _LookupValue As Object
Public Property LookupValue() As Object
Get
Return _LookupValue
End Get
Set(ByVal value As Object)
_LookupValue = value
End Set
End Property
Public Sub New()
Me._LookupValue = Nothing
If Me.OwningColumn Is Nothing Then Return
Me.ScalarStatement = CType(Me.OwningColumn, DataGridViewScalarValue2TextboxColumn).ScalarSQLStatement
End Sub
Public Property ScalarStatement() As String
Get
Return _scalarStatement
End Get
Set(ByVal Value As String)
_scalarStatement = Value
End Set
End Property
' Override the Clone method so that the Enabled property is copied.
Public Overrides Function Clone() As Object
Dim Cell As DataGridViewScalarValue2TextboxCell = CType(MyBase.Clone(), _
DataGridViewScalarValue2TextboxCell)
Cell._scalarStatement = Me._scalarStatement
Return Cell
End Function
Protected Overrides Function GetFormattedValue(ByVal value As Object, _
ByVal rowIndex As Integer, _
ByRef cellStyle As DataGridViewCellStyle, _
ByVal valueTypeConverter As TypeConverter, _
ByVal formattedValueTypeConverter As TypeConverter, _
ByVal context As DataGridViewDataErrorContexts) As Object
If _LookupValue <> value Then
_LookupValue = value
GetReturnValueFromLookupValue()
End If
Return MyBase.GetFormattedValue(_ReturnValue, rowIndex, cellStyle, _
valueTypeConverter, formattedValueTypeConverter, context)
End Function
Protected Overrides Sub Paint(ByVal graphics As Graphics, _
ByVal clipBounds As Rectangle, _
ByVal cellBounds As Rectangle, _
ByVal rowIndex As Integer, _
ByVal cellState As DataGridViewElementStates, _
ByVal value As Object, _
ByVal formattedValue As Object, _
ByVal errorText As String, _
ByVal cellStyle As DataGridViewCellStyle, _
ByVal advancedBorderStyle As DataGridViewAdvancedBorderStyle, _
ByVal paintParts As DataGridViewPaintParts)
If value IsNot Nothing AndAlso _
(TypeOf value Is String AndAlso Not String.IsNullOrEmpty(value)) OrElse _
(TypeOf value Is Integer AndAlso Integer.TryParse(value, Nothing)) OrElse _
(TypeOf value Is Decimal AndAlso Decimal.TryParse(value, 0)) OrElse _
(TypeOf value Is Date) AndAlso IsDate(value) Then
_LookupValue = value
GetReturnValueFromLookupValue()
Else
_ReturnValue = String.Empty
End If
MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, _
_ReturnValue, errorText, cellStyle, advancedBorderStyle, paintParts)
End Sub
Private Sub GetReturnValueFromLookupValue()
If _LookupValue Is Nothing _
Or OwningColumn Is Nothing _
Or String.IsNullOrEmpty(_scalarStatement) Then
_ReturnValue = Nothing
Return
End If
Using conn As New SqlClient.SqlConnection(ConnUtils.MyGCPTableConnectionString)
conn.Open()
Dim cmd As SqlClient.SqlCommand = conn.CreateCommand
With cmd
.CommandText = String.Format(_scalarStatement, _LookupValue)
.CommandType = CommandType.Text
Dim objResult = .ExecuteScalar
If objResult IsNot Nothing Then
_ReturnValue = objResult
End If
End With
conn.Close()
End Using
End Sub
Public Overloads Property Value() As Object
Get
Return _ReturnValue
End Get
Set(ByVal value)
If TypeOf value Is Integer AndAlso value > 0 Then
If _LookupValue <> value Then
_LookupValue = value
GetReturnValueFromLookupValue()
End If
End If
End Set
End Property
End Class
I did the same for the DataGridViewTextboxColumn class. Ireferences its main property (for this usage) in my original post. This subclass has this code in it:
Imports System.ComponentModel
Public Class DataGridViewScalarValue2TextboxColumn
Inherits DataGridViewTextBoxColumn
Public Sub New()
Me.CellTemplate = New DataGridViewScalarValue2TextboxCell()
End Sub
'' Fields...
Private _scalarSQLStatement As String = String.Empty
<Browsable(True), _
EditorBrowsable(EditorBrowsableState.Always), _
Category("Data"), _
Description("The SQL Query to use for lookup. Make sure it will work with string.format"), _
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)> _
Public Property ScalarSQLStatement() As String
Get
Return _scalarSQLStatement
End Get
Set(ByVal Value As String)
_scalarSQLStatement = Value
End Set
End Property
In the form I use the CellFormatting and RowsAdded events like below:
Private Sub dgvCustomCellTypes_CellFormatting(ByVal sender As Object, ByVal e As .DataGridViewCellFormattingEventArgs) Handles dgvCustomCellTypes.CellFormatting
If e.ColumnIndex = colHaulID2.Index Then
Dim dgvr As DataGridViewRow = CType(sender, DataGridView).Rows(e.RowIndex)
Dim HaulCell As DataGridViewScalarValue2TextboxCell = CType(dgvr.Cells(colHaulID2.Index), DataGridViewScalarValue2TextboxCell)
HaulCell.ScalarStatement = colHaulID2.ScalarSQLStatement
End If
End Sub
Private Sub dgvCustomCellTypes_RowsAdded(ByVal sender As Object, ByVal e As DataGridViewRowsAddedEventArgs) Handles dgvCustomCellTypes.RowsAdded
For i As Integer = e.RowIndex To e.RowCount - 1
Dim dgvr As DataGridViewRow = CType(sender, DataGridView).Rows(i)
Dim HaulCell As DataGridViewScalarValue2TextboxCell = CType(dgvr.Cells(colHaulID2.Index), DataGridViewScalarValue2TextboxCell)
HaulCell.ScalarStatement = colHaulID2.ScalarSQLStatement
Next
End Sub
And added this to the form initialization:
colHaulID2.ScalarSQLStatement = "Select CompanyName From HaulCompany Where HaulID = {0}"
The column is databound and gets its lookup value there. It works fine, but is not the solution I'm looking for.
I am trying to rewrite the cell/column so that I can just add the SQL statement to the column using the column editor at design time and rely on the databound value to provide a lookup key.
The only thing, in my mind, that is stopping me from achieving my goal, is getting the control to save it's SQL statement after I close the column editor at design time. Or prevent it from getting cleared when I reopen the column editor. I'm not sure which is happening. But I'm sure there is a solution. I've made a few attempts at persistence, including a VB6-like property bag example I found. I am ignoring saving the property to disk. Seems there should be a way to let the application handle this task. So far, nothing has worked for me
END EDIT
Any help would be greatly appreciated.
Thanks.
Marshall
Double-click a cell in the first column for the demo.
Public Class Form1
Sub New()
' This call is required by the designer.'
InitializeComponent()
' Add any initialization after the InitializeComponent() call.'
Dim dtb As New DataTable
dtb.Columns.Add("C1")
dtb.Columns.Add("C2")
dtb.Columns.Add("C3")
dtb.Rows.Add("1", "2", "3")
dtb.Rows.Add("2", "3", "4")
dtb.Rows.Add("3", "4", "5")
dtb.Rows.Add("4", "5", "6")
DataGridView1.DataSource = dtb
DataGridView1.Columns(0).Tag = "Select JobName From JobList where JobNo = {0}"
End Sub
Private Sub DataGridView1_CellMouseDoubleClick(sender As Object, e As DataGridViewCellMouseEventArgs) Handles DataGridView1.CellMouseDoubleClick
Dim strSql As String = String.Format(DataGridView1.Columns(e.ColumnIndex).Tag.ToString, DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex).Value.ToString)
MsgBox(strSql)
End Sub
End Class
I found the solution I was looking for.
I needed to override the Clone method in the column (not the cell), and ensure the property was getting passed to the cell at runtime. Below I'm posting my final solution.
(I found my Solution here on Stack Overflow.)
Imports System.ComponentModel
Imports System.ComponentModel.Design
<Serializable()> _
Public Class DataGridViewScalarValueTextboxColumn
Inherits DataGridViewTextBoxColumn
Public Sub New()
Me.CellTemplate = New DataGridViewScalarValueTextboxCell()
End Sub
' Fields...'
Private _scalarSQLStatement As String = String.Empty
<Browsable(True), _
Category("Data"), _
Description("The SQL Query to use for lookup. Must work with the string.format function if lookup value is used."), _
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)> _
Public Property ScalarSQLStatementCol() As String
Get
Return _scalarSQLStatement
End Get
Set(ByVal Value As String)
_scalarSQLStatement = Value
End Set
End Property
Public Overrides Function Clone() As Object
Dim myClone As DataGridViewScalarValueTextboxColumn = CType(MyBase.Clone, DataGridViewScalarValueTextboxColumn)
myClone.ScalarSQLStatementCol = ScalarSQLStatementCol
Return myClone
End Function
End Class
Public Class DataGridViewScalarValueTextboxCell
Inherits DataGridViewTextBoxCell
Private _ReturnValue As String = String.Empty
Private _LookupValue As Object
Public Property LookupValue() As Object
Get
Return _LookupValue
End Get
Set(ByVal value As Object)
_LookupValue = value
End Set
End Property
Public Sub New()
Me._LookupValue = Nothing
End Sub
Public Overrides Function Clone() As Object
'Method may not be needed now'
Dim Cell As DataGridViewScalarValueTextboxCell = CType(MyBase.Clone(), _
DataGridViewScalarValueTextboxCell)
Return Cell
End Function
Protected Overrides Function GetFormattedValue(ByVal value As Object, _
ByVal rowIndex As Integer, _
ByRef cellStyle As DataGridViewCellStyle, _
ByVal valueTypeConverter As TypeConverter, _
ByVal formattedValueTypeConverter As TypeConverter, _
ByVal context As DataGridViewDataErrorContexts) As Object
If _LookupValue <> value Then
_LookupValue = value
GetReturnValueFromLookupValue()
End If
Return MyBase.GetFormattedValue(_ReturnValue, rowIndex, cellStyle, _
valueTypeConverter, formattedValueTypeConverter, context)
End Function
Protected Overrides Sub Paint(ByVal graphics As Graphics, _
ByVal clipBounds As Rectangle, _
ByVal cellBounds As Rectangle, _
ByVal rowIndex As Integer, _
ByVal cellState As DataGridViewElementStates, _
ByVal value As Object, _
ByVal formattedValue As Object, _
ByVal errorText As String, _
ByVal cellStyle As DataGridViewCellStyle, _
ByVal advancedBorderStyle As DataGridViewAdvancedBorderStyle, _
ByVal paintParts As DataGridViewPaintParts)
If value IsNot Nothing AndAlso _
(TypeOf value Is String AndAlso Not String.IsNullOrEmpty(value)) OrElse _
(TypeOf value Is Integer AndAlso Integer.TryParse(value, Nothing)) OrElse _
(TypeOf value Is Decimal AndAlso Decimal.TryParse(value, 0)) OrElse _
(TypeOf value Is Date) AndAlso IsDate(value) Then
_LookupValue = value
GetReturnValueFromLookupValue()
Else
_ReturnValue = String.Empty
End If
MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, _
_ReturnValue, errorText, cellStyle, advancedBorderStyle, paintParts)
End Sub
Private Sub GetReturnValueFromLookupValue()
If _LookupValue Is Nothing _
Or (OwningColumn Is Nothing _
OrElse CType(Me.OwningColumn, DataGridViewScalarValue2TextboxColumn).ScalarSQLStatementCol.Length = 0) Then
_ReturnValue = Nothing
Return
End If
Using conn As New SqlClient.SqlConnection(ConnUtils.MyGCPTableConnectionString)
conn.Open()
Dim cmd As SqlClient.SqlCommand = conn.CreateCommand
With cmd
.CommandText = String.Format(CType(Me.OwningColumn, DataGridViewScalarValue2TextboxColumn).ScalarSQLStatementCol, _LookupValue)
.CommandType = CommandType.Text
Dim objResult = .ExecuteScalar
If objResult IsNot Nothing Then
_ReturnValue = objResult
End If
End With
conn.Close()
End Using
End Sub
Public Overloads Property Value() As Object
Get
Return _ReturnValue
End Get
Set(ByVal value)
If TypeOf value Is Integer AndAlso value > 0 Then
If _LookupValue <> value Then
_LookupValue = value
GetReturnValueFromLookupValue()
End If
End If
End Set
End Property
End Class
I'm sure there is more refactoring that can be done, but it works. Just add the column to your grid, set the bind property for lookup and set the SQL Statement (must return just one value) and run it. Only had to load the source data.
Thanks all.
I've been working on a computing project in Visual Basic 2010 and I've encountered a error and I cannot find the problem causing it.
Whenever I try to save to my database I get a error in my OleDBConnection.vb form with the following
This is my Code
Class OleDbConnection
Private _p1 As Object
Sub New(ByVal p1 As Object)
' TODO: Complete member initialization
_p1 = p1
End Sub
Sub Close()
Throw New NotImplementedException
End Sub
Sub Open()
**Throw New NotImplementedException**
End Sub
End Class
my original code for the saving of the project to the database is
Public Class AddClass
Public Shared connectionString As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data
Source=C:\Users\Sami\Desktop\F454\VB_PrototypeDB.accdb;Persist Security Info=False;"
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub DataView()
Throw New NotImplementedException
End Sub
Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click
Me.Hide()
MMenu.Show()
End Sub
Private Sub PictureBox2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox2.Click
Dim cn As New OleDbConnection(connectionString)
cn.Open()
Dim ClassName As String = TextBox1.Text
Dim Subject As String = ComboBox1.Text()
Dim cmd As New OleDbCommand("INSERT INTO Class (ClassName, Subject) VALUES ('" & ClassName & "', '" & Subject & "') ')", cn)
cmd.ExecuteNonQuery()
cn.Close()
Call MsgBox("Student Has Been Saved", 0, "Student Database")
End Sub
End Class
I wrote the following code:
Public Class Form1
Private Structure udtThing
Dim SomeText As String
Dim SomeElements() As String
Public Shared Function CreateInstance() As udtThing
Dim result As New udtThing
result.SomeText = String.Empty
ReDim result.SomeElements(2)
result.SomeElements(0) = String.Empty
result.SomeElements(1) = String.Empty
result.SomeElements(2) = String.Empty
Return result
End Function
End Structure
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim nThings() As udtThing
nThings = Array.CreateInstance(GetType(udtThing), 10)
End Sub
End Class
I partly works, nThings becomes an array of 11 udtThings.
But .SomeElements is not redimmed to 3 strings of String.Empty, but it is "Nothing" instead.
Does anybody see where I went wrong?
Thank you very much!
By design, a Redim is required. Array.CreateInstance() isn't going to perform that operation, it can't guess what size is required. You'll have to help:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim nThings(10) As udtThing
For ix As Integer = 0 To UBound(nThings)
nThings(ix) = udtThing.CreateInstance()
Next
End Sub
The following is the code which I am using for creating a two dimensional array
Public Class frmGrades
Private Score As Dictionary(Of String, String) = New Dictionary(Of String, String)
Private Sub cmdApply_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdApply.Click
Static intNumber As Integer
ReDim Preserve Score(1, intNumber)
Score(0, intNumber) = txtStudent.Text
Score(1, intNumber) = txtGrade.Text
hsbStudent.Maximum = intNumber
hsbStudent.Value = intNumber
intNumber = intNumber + 1
txtGrade.Clear()
txtStudent.Clear()
txtStudent.Focus()
End Sub
Private Sub hsbStudent_Scroll(ByVal sender As Object, ByVal e As System.Windows.Forms.ScrollEventArgs) Handles hsbStudent.Scroll
txtStudent.Text = Score(0, hsbStudent.Value)
txtGrade.Text = Score(1, hsbStudent.Value)
End Sub
Private Sub cmdFirst_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdFirst.Click
txtStudent.Text = Score(0, hsbStudent.Minimum)
txtGrade.Text = Score(1, hsbStudent.Minimum)
End Sub
Private Sub cmdLast_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdLast.Click
txtStudent.Text = Score(0, hsbStudent.Maximum)
txtGrade.Text = Score(1, hsbStudent.Maximum)
End Sub
**Private Sub CmdEdit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CmdEdit.Click
If Score.ContainsKey(txtStudent.Text) Then
Score.Item(txtStudent.Text) = txtGrade.Text
Else
Score.Add(txtStudent.Text, txtGrade.Text)
End If
End Sub
End Class**
Now I would like to edit the grade text box which should also change the grade in the array. Any idea on how could this be done.
UPDATE:
OK, I see the code has a little more now and the problem is more clear as well as the need for a different approach. I rewrote your code. See if this works for you:
Public Class frmGrades
Public Class StudentGrade
Public Name As String
Public Grade As String
End Class
Private Score As List(Of StudentGrade) = New List(Of StudentGrade)
Private Sub cmdApply_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdApply.Click
AddStudent()
End Sub
Private Sub hsbStudent_Scroll(ByVal sender As Object, ByVal e As System.Windows.Forms.ScrollEventArgs) Handles hsbStudent.Scroll
Update(hsbStudent.Value - 1) 'lists are zero-based
End Sub
Private Sub cmdFirst_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdFirst.Click
Update(0)
End Sub
Private Sub cmdLast_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdLast.Click
Update(hsbStudent.Maximum - 1)
End Sub
Private Sub AddStudent()
Dim nm As New StudentGrade
nm.Name = txtStudent.Text
nm.Grade = txtGrade.Text
Score.Add(nm)
hsbStudent.Minimum = 1
hsbStudent.Maximum = Score.Count
hsbStudent.Value = Score.Count
txtGrade.Clear()
txtStudent.Clear()
txtStudent.Focus()
End Sub
Private Sub Update(i As Integer)
txtStudent.Text = Score(i).Name
txtGrade.Text = Score(i).Grade
End Sub
Private Sub CmdEdit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CmdEdit.Click
Dim index As Integer = -1, cnt As Integer = 0
For Each nm As StudentGrade In Score
If nm.Name.ToLower = txtStudent.Text.ToLower Then
index = cnt
Exit For
End If
cnt += 1
Next
If index = -1 Then
AddStudent()
Else
Score(index).Name = txtStudent.Text
Score(index).Grade = txtGrade.Text
hsbStudent.Value = index + 1
End If
End Sub
End Class
With this code you can expand the class StudentGrade to contain whatever you need to store. The list, contrary to dictionary, allow you to use index values as well.
What's wrong in my code? It's not updating the TextBox and the ProgressBar while deleting files.
Imports System.Windows.Threading
Imports System.IO
Class MainWindow
Private Sub bt_Click(ByVal sender As Object,
ByVal e As RoutedEventArgs) Handles bt.Click
Dim sb As New System.Text.StringBuilder
Dim files = IO.Directory.EnumerateFiles(
My.Computer.FileSystem.SpecialDirectories.Temp, "*.*",
SearchOption.TopDirectoryOnly)
Dim count = files.Count
pb.Minimum = 0
pb.Maximum = count
For i = 0 To count - 1
Dim f = files(i)
Dispatcher.BeginInvoke(
New Action(Of String, Integer)(
Sub(str, int)
tb.SetValue(TextBox.TextProperty, str)
pb.SetValue(ProgressBar.ValueProperty, int)
End Sub),
DispatcherPriority.Send,
f, i + 1)
Try
File.Delete(f)
Catch ex As Exception
sb.AppendLine(f)
End Try
Dim exceptions = sb.ToString
Stop
Next
End Sub
End Class
I got this working with the BackgroundWorker object. This places your work in a background thread, with calls to update the UI going through the ProgressChanged event. I also used Invoke instead of BeginInvoke within the work loop, which forces the loop to wait for the UI to become updated before it proceeds.
Imports System.ComponentModel
Imports System.IO
Class MainWindow
Private WithEvents bw As New BackgroundWorker
Private Sub Button1_Click(ByVal sender As System.Object,
ByVal e As RoutedEventArgs) Handles btn.Click
pb.Minimum = 0
pb.Maximum = 100
bw.WorkerReportsProgress = True
bw.RunWorkerAsync()
End Sub
Private Sub bw_DoWork(ByVal sender As Object,
ByVal e As DoWorkEventArgs) Handles bw.DoWork
Dim sb As New System.Text.StringBuilder
Dim files = IO.Directory.EnumerateFiles(
My.Computer.FileSystem.SpecialDirectories.Temp, "*.*",
SearchOption.TopDirectoryOnly)
Dim count = files.Count
Me.Dispatcher.BeginInvoke(Sub()
tb.Text = "SOMETHING ELSE"
End Sub)
For i = 0 To count - 1
Dim f = files(i)
Dim myI = i + 1
Me.Dispatcher.Invoke(
Sub()
bw.ReportProgress(CInt((myI / count) * 100), f)
End Sub)
'Try
' File.Delete(f)
'Catch ex As Exception
' sb.AppendLine(f)
'End Try
Dim exceptions = sb.ToString
'Stop
Next
End Sub
Private Sub bw_ProgressChanged(
ByVal sender As Object,
ByVal e As ProgressChangedEventArgs) Handles bw.ProgressChanged
Dim fString As String = TryCast(e.UserState, String)
Me.Dispatcher.BeginInvoke(Sub()
tb.Text = fString
End Sub)
End Sub
End Class