DataGridView Custom Column with special properties - sql-server

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.

Related

Error while checking row, after Filtering observational collection

I'm having trouble with editing an observable collection... I bind columns in codebehind like so...
Dim dgCheckBoxColumn As New DataGridCheckBoxColumn()
Dim column_username As New DataGridTextColumn()
Dim textColumn2 As New DataGridTextColumn()
Dim textColumn3 As New DataGridTextColumn()
dgCheckBoxColumn.Header = "Selected"
dgCheckBoxColumn.Binding = New Binding("Selected")
dgvResults.Columns.Add(dgCheckBoxColumn)
column_username.Header = "User Name"
column_username.Binding = New Binding("accountName")
dgvResults.Columns.Add(column_username)
textColumn2.Header = "First Name"
textColumn2.Binding = New System.Windows.Data.Binding("firstName")
dgvResults.Columns.Add(textColumn2)
textColumn3.Header = "Last Name"
textColumn3.Binding = New System.Windows.Data.Binding("lastName")
dgvResults.Columns.Add(textColumn3)
Then I create my observablecollection...
Dim oc_userlist As New ObservableCollection(Of user)
Imports System.ComponentModel
Public Class user
Implements INotifyPropertyChanged
Private m_accountname As String
Private m_firstname As String
Private m_lastname As String
Private _Selected As Boolean
Public Property Selected() As Boolean
Get
Return _Selected
End Get
Set(value As Boolean)
_Selected = value
NotifyPropertyChanged("IsChecked")
End Set
End Property
Public Property accountName() As String
Get
Return m_accountname
End Get
Set(value As String)
m_accountname = value
End Set
End Property
Public Property firstName() As String
Get
Return m_firstname
End Get
Set(value As String)
m_firstname = value
End Set
End Property
Public Property lastName() As String
Get
Return m_lastname
End Get
Set(value As String)
m_lastname = value
End Set
End Property
#Region "INotifyPropertyChanged Members"
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
#End Region
#Region "Private Helpers"
Private Sub NotifyPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
End Class
Then I add new user to collection, like so...
oc_userlist.Add(New user With { _
.Selected = False, _
.accountName = "awiles", _
.firstName = "Anthony", _
.lastName = "Wiles"
})
I'm trying to filter using a textbox, with the textchanged event, and it's working wonderfully...
Private Sub TextBox_TextChanged(sender As Object, e As TextChangedEventArgs)
Dim result = oc_userlist.Where(Function(w) w.accountName.Contains(txtFilter.Text.ToString) _
Or w.firstName.Contains(txtFilter.Text.ToString) _
Or w.lastName.Contains(txtFilter.Text.ToString))
dgvResults.ItemsSource = result
End Sub
The problem I'm having is once this is filtered, using the textbox, then I try to select something, it tells me
'EditItem' is not allowed for this view.
I'm having issues trying to get around this issue, can anybody point me in the correct direction?
The reason could be the result of your Linq query (with the Where) is not a List(Of T) but an IEnumerable.
Try adding a call to ToList()
Dim result = oc_userlist.Where(Function(w) w.accountName.Contains(txtFilter.Text.ToString) _
Or w.firstName.Contains(txtFilter.Text.ToString) _
Or w.lastName.Contains(txtFilter.Text.ToString)) _
.ToList()
Then tell if it works

How to check empty array

I am working in VB.net where i have class like below:
Public Class vertex
Public wasVisited As Boolean
Public name, type As String
Public x_pos, y_pos As Double
Public Sub New(ByVal x_pos As Double, ByVal y_pos As Double, ByVal name As Integer, ByVal type As String)
Me.x_pos = x_pos
Me.y_pos = y_pos
Me.name = name
Me.type = type
wasVisited = False
End Sub
End Class
I have object of some other class named as "graph" where in constructor of graph class I am calling constructor of vertex class.
I have array of vertex class: Public vertices() As vertex
And redim vertices(2000): resizing array again for some reason.
Now, when i loop the array to check empty value it throws an error:
Object reference not set to an instance of an object. (Since value contains "nothing")
even though i am checking like this,
If (vertices(i).name) Is Nothing Then
Exit For
End If
How can i check empty element of array?
Since you seem to want your collection be dynamic, a List(Of vertex) would serve you better. that along with a default New() constructor and you can add, remove, sort, search, whatever you need. To check for any empty value you can use If Vertices(i).name = "" then
Public Class vertex
Public wasVisited As Boolean
Public name, type As String
Public x_pos, y_pos As Double
Public Sub New()
wasVisited = False
name = ""
type = ""
x_pos = 0
y_pos = 0
End Sub
Public Sub New(ByVal x_pos As Double, ByVal y_pos As Double, ByVal name As String, ByVal type As String)
Me.x_pos = x_pos
Me.y_pos = y_pos
Me.name = name
Me.type = type
wasVisited = False
End Sub
End Class
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim Vertices As New List(Of vertex)
For I = 0 To 99
Vertices.Add(New vertex())
Vertices(I).name = "Test" + I.ToString
Next
End Sub
What's the size of vertices() before the redim operation ? If it's less than 2000, then the added elements will be Nothing right after the array enlargement, therefore when you try to access the name property of vertices(i) for values of i that go beyond the initial array size you're actually trying to dereference a null object reference.
You either need to check that vertices(i) IsNot Nothing before testing for the value of its properties or make sure every element of the array is assigned a new vertex object.
If vertices(i) Is Nothing OrElse vertices(i).name Is Nothing Then
Exit For
End If
Here's a thread on vbforums about a similar problem: http://www.vbforums.com/showthread.php?546668-RESOLVED-Redim-array-of-objects
Have you tried:
If Not vertices Is Nothing AndAlso Not vertices(i) Is Nothing _
AndAlso Not vertices(i).name Is Nothing Then
Dim value as string= vertices(i).name
End If

Is there a way to prevent a user selecting another row when DataGrid is in edit mode?

I want, that if a cell/row goes to edit mode, then, if the user attempts to select a different row, it should try to commit that row, if the row is not committed sucessfully, it should decline the selection request and the editing row should remain selected and in edit mode.
Do you have experience with a good helper for it? any good workaround?
NOTE: I've been struggling with this issue for a long time and already got some experience, so please post only working examples, not random thoughts.
The code bellow is includes extension from here (code normalized to fit StackOverflow screen witdh, sorry).
Imports System.ComponentModel
Imports System.Windows.Threading
Namespace Components
Public NotInheritable Class DataGridSelectionChangingBehavior
Public Shared Function GetEnableSelectionChanging(
ByVal element As DataGrid) As Boolean
If element Is Nothing Then Throw New ArgumentNullException("element")
Return element.GetValue(EnableSelectionChangingProperty)
End Function
Public Shared Sub SetEnableSelectionChanging(
ByVal element As DataGrid, ByVal value As Boolean)
If element Is Nothing Then Throw New ArgumentNullException("element")
element.SetValue(EnableSelectionChangingProperty, value)
End Sub
Public Shared ReadOnly EnableSelectionChangingProperty As _
DependencyProperty =
DependencyProperty.RegisterAttached("EnableSelectionChanging",
GetType(Boolean),
GetType(DataGridSelectionChangingBehavior),
New FrameworkPropertyMetadata(False,
New PropertyChangedCallback(
AddressOf EnableSelectionChanging_PropertyChanged)))
Public Shared Sub AddSelectionChangingHandler(
ByVal element As DataGrid, handler As SelectionChangingEventHandler)
If element IsNot Nothing Then _
element.AddHandler(
DataGridSelectionChangingBehavior.SelectionChangingEvent, handler)
End Sub
Public Shared Sub RemoveSelectionChangingHandler(
ByVal element As DataGrid, handler As SelectionChangingEventHandler)
If element IsNot Nothing Then _
element.RemoveHandler(
DataGridSelectionChangingBehavior.SelectionChangingEvent, handler)
End Sub
Public Shared ReadOnly SelectionChangingEvent As RoutedEvent =
EventManager.RegisterRoutedEvent("SelectionChanging",
RoutingStrategy.Bubble,
GetType(SelectionChangingEventHandler),
GetType(DataGridSelectionChangingBehavior))
Private Shared Sub EnableSelectionChanging_PropertyChanged(
ByVal sender As Object, ByVal e As DependencyPropertyChangedEventArgs)
Dim dataGrid = DirectCast(sender, DataGrid)
If CBool(e.NewValue) Then
AddHandler dataGrid.PreparingCellForEdit,
AddressOf DataGrid_PreparingCellForEdit
AddHandler dataGrid.SelectionChanged,
AddressOf DataGrid_SelectionChanged
Else
RemoveHandler dataGrid.PreparingCellForEdit,
AddressOf DataGrid_PreparingCellForEdit
RemoveHandler dataGrid.SelectionChanged,
AddressOf DataGrid_SelectionChanged
RecentColumn.Remove(dataGrid)
End If
End Sub
Private Shared Sub DataGrid_SelectionChanged(
ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
If e.RemovedItems.Count = 0 Then Exit Sub
Dim dataGrid = DirectCast(sender, DataGrid)
Dim removed = e.RemovedItems(0)
Dim row = dataGrid.GetContainerFromItem(Of DataGridRow)(removed)
Dim scea As New SelectionChangingEventArgs(row,
DataGridSelectionChangingBehavior.SelectionChangingEvent, dataGrid)
dataGrid.RaiseEvent(scea)
If scea.Cancel Then
RemoveHandler dataGrid.SelectionChanged,
AddressOf DataGrid_SelectionChanged
Dim operation = dataGrid.Dispatcher.BeginInvoke(
Sub()
dataGrid.SelectedItem = removed
Dim column As DataGridColumn = Nothing
If RecentColumn.TryGetValue(dataGrid, column) Then
Dim cellsPanel =
row.GetVisualDescendant(Of DataGridCellsPanel)().
Children.Cast(Of DataGridCell)()
Dim recentCell =
If(cellsPanel.SingleOrDefault(
Function(cell) cell.Column Is column),
cellsPanel.FirstOrDefault)
If recentCell IsNot Nothing Then
recentCell.IsEditing = True
Keyboard.Focus(recentCell)
End If
End If
End Sub,
DispatcherPriority.ContextIdle)
AddHandler operation.Completed,
Sub(s, ea) AddHandler dataGrid.SelectionChanged,
AddressOf DataGrid_SelectionChanged
End If
End Sub
Private Shared m_RecentColumn As Dictionary(Of DataGrid, DataGridColumn)
Public Shared ReadOnly Property RecentColumn() As Dictionary(Of DataGrid,
DataGridColumn)
Get
If m_RecentColumn Is Nothing Then m_RecentColumn =
New Dictionary(Of DataGrid, DataGridColumn)()
Return m_RecentColumn
End Get
End Property
Private Shared Sub DataGrid_PreparingCellForEdit(
ByVal sender As Object, e As DataGridPreparingCellForEditEventArgs)
Dim dataGrid = DirectCast(sender, DataGrid)
RecentColumn(dataGrid) = e.Column
End Sub
End Class
Public Delegate Sub SelectionChangingEventHandler(
ByVal sender As Object, ByVal e As SelectionChangingEventArgs)
Public Class SelectionChangingEventArgs : Inherits RoutedEventArgs
Public Sub New(ByVal row As DataGridRow, routedEvent As RoutedEvent)
MyBase.New(routedEvent)
m_CurrentRow = row
End Sub
Public Sub New(ByVal row As DataGridRow,
ByVal routedEvent As RoutedEvent,
ByVal source As Object)
MyBase.New(routedEvent, source)
m_CurrentRow = row
End Sub
Private m_CurrentRow As DataGridRow
Public ReadOnly Property CurrentRow() As DataGridRow
Get
Return m_CurrentRow
End Get
End Property
Public ReadOnly Property Item() As Object
Get
If CurrentRow IsNot Nothing Then Return CurrentRow.Item
Return Nothing
End Get
End Property
Public Property Cancel As Boolean
End Class
End Namespace
Usage:
<DataGrid Name="dg"
src:DataGridSelectionChangingBehavior.EnableSelectionChanging="True"
src:DataGridSelectionChangingBehavior.SelectionChanging="dg_SelectionChanging">
Code behind (pseudu):
Private Sub dg_SelectionChanging(ByVal sender As Object,
ByVal e As SelectionChangingEventArgs)
If e.CurrentRow.IsEditing Then
Dim item = TryCast(e.CurrentRow.Item, MyEntityType)
If item IsNot Nothing AndAlso item.IsValid Then
Dim dataGrid = DirectCast(sender, DataGrid)
Dim committed = dataGrid.CommitEdit(DataGridEditingUnit.Row, True)
If committed Then Exit Sub
End If
e.Cancel = True
End If
End Sub
Note: visual studio might not show the event in the intellisense and might even generate a design-time error, but it should compile and work perfect.
FYI: code formatted modified to fit SO screen.

VB.NET DataGridView "An item with the same key has already been added." while using a unique index

VB.NET 2010, .NET 4
Hello,
I've looked around and can't seem to find a solution to my problem. I have an EventLog object which inherits DataGridView and has a public variable EventList which is a List(Of EventLogItem). EventLogItem has seven properties which describe the event, including Index which is set to EventList.Count each time an entry is added (so, it should be unique). Everything has worked just fine until I tried to add an entry from a serial port DataReceived event handler upon which I receive the following exception:
An error occurred creating the form. See Exception.InnerException for details. The error is: An item with the same key has already been added.
Clicking 'View Details' and expanding the InnerException yields no more information. Here is some relevant code:
The EventLog class with its EventList:
Public Class EventLog
Inherits DataGridView
Public EventList As New List(Of EventLogItem)
..Column creation code, etc..
End Class
The EventLogItem class:
Public Class EventLogItem
Public Property Index As Integer
Public Property Timestamp As String
Public Property User As String = String.Empty
Public Property [Step] As String = String.Empty
Public Property Type As Types
Public Property Message As String
Public Property TypeIcon As Image
Public Enum Types
SeriousError = -2
NormalError = -1
Warning = 0
NormalEvent = 1
ImportantEvent = 2
ManualEntry = 3
End Enum
Private Sub New()
Me.Timestamp = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss.f")
Me.Type = Types.NormalEvent
SetTypeIcon()
End Sub
Public Sub New(ByVal Message As String)
Me.New()
Me.Message = Message
End Sub
Public Sub New(ByVal Message As String, ByVal User As String)
Me.New()
Me.Message = Message
Me.User = User
End Sub
Public Sub New(ByVal Message As String, ByVal User As String, ByVal Type As Types)
Me.New(Message, User)
Me.Type = Type
SetTypeIcon()
End Sub
Public Sub New(ByVal Message As String, ByVal User As String, ByVal Type As Types, ByVal [Step] As Integer)
Me.New(Message, User, Type)
Me.Step = ([Step] + 1).ToString
End Sub
Private Sub SetTypeIcon()
Select Case Me.Type
Case Types.NormalError
Me.TypeIcon = My.Resources.ErrorIcon
Case Types.SeriousError
Me.TypeIcon = My.Resources.ErrorIcon
Case Types.Warning
Me.TypeIcon = My.Resources.WarningIcon
Case Types.ManualEntry
Me.TypeIcon = My.Resources.ManualIcon
Case Else
Me.TypeIcon = My.Resources.OkayIcon
End Select
End Sub
End Class
Code for inserting an item into the event log:
Inside my main-form class:
Public Sub NewEventLogEntry(ByVal Message As String, ByVal ex As Exception, ByVal Type As EventLogItem.Types, ByVal IncludeProcessStepNumber As Boolean)
If IncludeProcessStepNumber Then
SafeInvokeControl(EventLog, Sub(x)
Dim FirstRow As Integer = x.FirstDisplayedScrollingRowIndex
Dim [Event] As New EventLogItem(Message, My.Settings.SelectedUser, Type, Device.CurrentProcessStep)
[Event].Index = x.RowCount + 1
x.EventList.Insert(0, [Event])
x.DataSource = GetType(List(Of EventLogItem))
x.DataSource = x.EventList
x.SetRowStyles()
If FirstRow >= 0 And FirstRow < x.RowCount Then x.FirstDisplayedScrollingRowIndex = FirstRow
End Sub)
Else
SafeInvokeControl(EventLog, Sub(x)
Dim FirstRow As Integer = x.FirstDisplayedScrollingRowIndex
Dim [Event] As New EventLogItem(Message, My.Settings.SelectedUser, Type)
[Event].Index = x.RowCount + 1
x.EventList.Insert(0, [Event])
x.DataSource = GetType(List(Of EventLogItem))
x.DataSource = x.EventList
x.SetRowStyles()
If FirstRow >= 0 And FirstRow < x.RowCount Then x.FirstDisplayedScrollingRowIndex = FirstRow
End Sub)
End If
If Type < EventLogItem.Types.Warning Then
Dim ErrorBox As New ErrorBox
Dim ErrorBoxThread As New Threading.Thread(AddressOf ErrorBox.ShowDialog)
ErrorBoxThread.IsBackground = True
ErrorBox.Exception = ex
If Type = EventLogItem.Types.NormalError Then
ErrorBox.Type = ErrorBox.Types.Error
ErrorBox.Message = Message
ElseIf Type = EventLogItem.Types.SeriousError Then
ErrorBox.Type = ErrorBox.Types.SeriousError
ErrorBox.Message = Message & vbNewLine & vbNewLine & "This is a serious error and indicates that the program is " & _
"unstable. The source of this error should be corrected before this program is used for anything important."
End If
StopMasterTimer()
ErrorBoxThread.Start()
End If
End Sub
end main-form class snippet
The code that's causing the problem (ch4cp is my namespace. this code resides in a class other than my main-form class):
Inside a serial port device class:
<Runtime.CompilerServices.MethodImplAttribute(Runtime.CompilerServices.MethodImplOptions.Synchronized)> _
Private Shared Sub Port_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles Port.DataReceived
..some code..
ch4cp.NewEventLogEntry("testing")
..some more code..
End Sub
End serial port device class snippet
Any ideas?
Thanks a lot in advance.
It sounds like you are getting a thread exception error in your code somewhere. The threading problem will cause other issues and not appear until you step through the code and look at each line being executed.
Try the following at the beginning of your code block to try to identify the problem.
Control.CheckForIllegalCrossThreadCalls = false

Raising event in custom control

I'm writing a custom textblock control thats populate hyperlinks and raises event when clicked to hyperlink.
I wrote this code but I got stucked.
My code is :
Imports System.Text.RegularExpressions
Public Class CustomTextBlock
Inherits TextBlock
Public Event Klik As EventHandler(Of EventArgs)
Public ReadOnly InlineCollectionProperty As DependencyProperty = DependencyProperty.Register("InlineCollection", GetType(String), GetType(CustomTextBlock), New PropertyMetadata(New PropertyChangedCallback(AddressOf CustomTextBlock.InlineChanged)))
Private Shared Sub InlineChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
DirectCast(sender, CustomTextBlock).Inlines.Clear()
Dim kelimeler = Split(e.NewValue, " ")
For i = 0 To kelimeler.Length - 1
If Regex.Match(kelimeler(i), "(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,#?^=%&:/~\+#]*[\w\-\#?^=%&/~\+#])?").Success Then
Dim x = New Hyperlink(New Run(kelimeler(i)))
x.AddHandler(Hyperlink.ClickEvent, New RoutedEventHandler(AddressOf t_Click))
x.ToolTip = kelimeler(i)
x.Tag = kelimeler(i)
DirectCast(sender, CustomTextBlock).Inlines.Add(x)
If Not i = kelimeler.Length Then DirectCast(sender, CustomTextBlock).Inlines.Add(" ")
Else
DirectCast(sender, CustomTextBlock).Inlines.Add(kelimeler(i))
If Not i = kelimeler.Length Then DirectCast(sender, CustomTextBlock).Inlines.Add(" ")
End If
''//Console.WriteLine(kelime(i).ToString.StartsWith("#"))
Next
kelimeler = Nothing
End Sub
Public Property InlineCollection As String
Get
Return DirectCast(GetValue(InlineCollectionProperty), String)
End Get
Set(ByVal value As String)
SetValue(InlineCollectionProperty, value)
End Set
End Property
Private Shared Sub t_Click(ByVal sender As Hyperlink, ByVal e As System.Windows.RoutedEventArgs)
e.Handled = True
RaiseEvent Klik(sender, EventArgs.Empty)
End Sub
End Class
This code gives error at RaiseEvent Klik(sender, EventArgs.Empty)
Error is : Cannot refer to an instance member of a class from within a shared method or shared member initializer without an expliticit instance of the class.
Thanks for your answers,
Alper
The problem is clearly stated in the exception message. The t_Click method is Shared (which means common to all instances of the class), so it cannot raise an Event that is specific to an instance of the class. You should only raise the event from a method that is not shared.
Do something like this -
Imports System
Imports System.Text.RegularExpressions
Public Class CustomTextBlock
Inherits TextBlock
Public Event Klik As EventHandler(Of System.EventArgs)
Public ReadOnly InlineCollectionProperty As DependencyProperty = DependencyProperty.Register("InlineCollection", GetType(String), GetType(CustomTextBlock), New PropertyMetadata(New PropertyChangedCallback(AddressOf CustomTextBlock.InlineChanged)))
Private Shared Sub InlineChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim d As CustomTextBlock = DirectCast(sender, CustomTextBlock)
d.Inlines.Clear()
d.OnInlineChanged(CType(e.NewValue, String))
End Sub
Private Sub OnInlineChanged(ByVal Value As String)
Dim kelimeler = Split(Value, " ")
For i As Integer = 0 To kelimeler.Length - 1
If Regex.Match(kelimeler(i), "(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,#?^=%&:/~\+#]*[\w\-\#?^=%&/~\+#])?").Success Then
Dim x = New Hyperlink(New Run(kelimeler(i)))
x.AddHandler(Hyperlink.ClickEvent, New RoutedEventHandler(AddressOf t_Click))
x.ToolTip = kelimeler(i)
x.Tag = kelimeler(i)
Me.Inlines.Add(x)
If Not i = kelimeler.Length Then Me.Inlines.Add(" ")
Else
Me.Inlines.Add(kelimeler(i))
If Not i = kelimeler.Length Then Me.Inlines.Add(" ")
End If
''//Console.WriteLine(kelime(i).ToString.StartsWith("#"))
Next
kelimeler = Nothing
End Sub
Public Property InlineCollection As String
Get
Return DirectCast(GetValue(InlineCollectionProperty), String)
End Get
Set(ByVal value As String)
SetValue(InlineCollectionProperty, value)
End Set
End Property
Private Sub t_Click(ByVal sender As Hyperlink, ByVal e As System.Windows.RoutedEventArgs)
e.Handled = True
RaiseEvent Klik(sender, System.EventArgs.Empty)
End Sub
End Class

Resources