DataTree ovvero Controllo TreeView collegato ad un DataSet
E rieccomi, dopo lunga assenza, a pubblicare un nuovo articolo relativo ad un controllo che ho recentemente realizzato (ancora molto ‘grezzo’ ma funzionante!)
La necessità è quella di avere un controllo TreeView che venga popolato da un DataSet e che ne gestisca l’inserimento, eliminazione o modifica degli elementi (nodi) in esso contenuti.
Vado a riportare un esempio:
Date tre tabelle (Articoli, Famiglie, Tipologie) tra loro relazionate, vogliamo visualizzare su un TreeView il nostro DataSet per ottenere un risultato di questo tipo:
Il controllo che ho realizzato che ovviamente eredita dal controllo TreeView, accetta un DataSet come sorgente dati contentente da 1 a 3 DataTable, per ciascun DataTable contenuto, dovremmo fornire passando degli array, le informazioni relative al nome dal DataTable, alla chiave primaria di ogni DataTable, nonché alla chiave di relazione con la tabella di livello superiore.
Tramite un Context menu attivato alla pressione del tasto destro del mouse in corrispondenza di uno dei nodi del DataTree, sarà possibile aggiungere un nuovo nodo, modificarne uno esistente, spostarlo in su o in giù rispetto alla posizione attuale.
Sintesi di Metodi e Proprietà personalizzate
PROPRIETA’ |
DESCRIZIONE |
|
|
DataSetOrigin |
Rappresenta il DataSet di origine passato in reference al controllo a runtime/designtime |
Tablename() |
Array contenente il nome dei DataTable (max 3) |
IDField() |
Array contenente il nome dei campi chiave delle tabelle |
DescriptionField() |
Array contenente il nome dei campi descrizione (visualizzati come label dei nodi) |
ParentField() |
Array contenente il nome dei campi chiave della tabella parent di 1° livello |
GrandParentField() |
Array contenente il nome dei campi chiave della tabella parent di 2° livello |
|
|
METODI |
DESCRIZIONE |
|
|
MoveUp (CurNode) |
Sposta verso l’alto il nodo selezionato |
MoveDown (CurNode) |
Sposta verso il basso il nodo selezionato |
DeleteNode (CurNode) |
Elimina il nodo selezionato |
AddNode (CurNode) |
Aggiunge un nuovo nodo figlio rispetto al nodo selezionato |
AddRootNode |
Aggiunge un Nodo al primo livello (root) |
ShowData |
Genera e visualizza i nodi |
|
|
EVENTI |
DESCRIZIONE |
|
|
BeforeDeleteNode |
Evento generato prima dell’eliminazione di un nodo, consente di controllare l’eliminazione dei nodi. |
Esempio di utilizzo:
Una volta compilato e generato il controllo (allego sotto il codice sorgente), aggiungerlo ai controlli della nostra soluzione.
Creiamo un DataSet contenente 3 DataTable (che chiameremo Ds):
1. FAMIGLIE |
Campo |
Tipo |
CdFamiglia |
Decimal |
DsFamiglia |
Text |
2. TIPOLOGIE |
Campo |
Tipo |
CdFamiglia |
Decimal |
CdTipologia |
Decimal |
DsTipologia |
Text |
3. ARTICOLI |
Campo |
Tipo |
CdFamiglia |
Decimal |
CdTipologia |
Decimal |
CdArticolo |
Decimal |
DsArticolo |
Text |
Trasciniamo il controllo nella nostra Form e denominiamolo TreeArticoli
A questo punto inizializziamo il nostro controllo impostando le relative proprietà:
With Me.TreeArticoli
.DataSetOrigin = DS
.TableName(0) = "Famiglie"
.IDField(0) = "CdFamiglia"
.DescriptionField(0) = "DsFamiglia"
.TableName(1) = "Tipologie"
.ParentField(0) = "CdFamiglia"
.IDField(1) = "CdTipologia"
.DescriptionField(1) = "DsTipologia"
.TableName(2) = "Articoli"
.GrandParentField = "CdFamiglia"
.ParentField(1) = "CdTipologia"
.IDField(2) = "CdArticolo"
.DescriptionField(2) = "DsArticolo"
.ShowData()
End With
Mandando in esecuzione la nostra applicazione vedremo che il nostro DataTree si popolerà automaticamente con i dati provenienti dal DataSet passato.
Ora attraverso il metodo AddRootNode potremo aggiungere nodi di primo livello (root), la pressione del tasto destro del mouse in corrispondenza di un nodo esistente mi consentirà di spostarlo, eliminarlo o aggiungere un nodo figlio rispetto a quello selezionato.
Il controllo è ancora in fase di beta testing, per cui non ne garantisco il perfetto funzionamento, consigli o segnalazioni sono ben accetti.
ED ECCO IL CODICE RELATIVO AL CONTROLLO
Imports System.ComponentModel
Public NotInheritable Class DataTree
Inherits System.Windows.Forms.TreeView
#Region "Dichiarazioni"
Dim Ds As DataSet
Dim StrTableName(2) As String
Dim StrIDField(2) As String
Dim StrFieldDescription(2) As String
Dim StrFieldParent(1) As String
Dim StrFieldGrandParent As String
Dim StrSeparatore As String
#End Region
#Region "Proprietà"
<Bindable(True), CategoryAttribute("DataBinding"), _
DescriptionAttribute("Imposta/Ottiene il Dataset contenente le tabelle collegate al controllo")> _
Public Property DataSetOrigin() As DataSet
' Restituisce/Imposta il Dataset collegato al treeview
Get
Return Ds
End Get
Set(ByVal value As DataSet)
Ds = value
End Set
End Property
<Bindable(True), CategoryAttribute("DataBinding"), _
DescriptionAttribute("Imposta la denominazione delle tabelle collegate al controllo")> _
Public Property TableName() As String()
' Restituisce/Imposta il nome delle tabelle contenente i dati
' da collegare al TreeView
Get
Return StrTableName
End Get
Set(ByVal value As String())
StrTableName = value
End Set
End Property
<Bindable(True), CategoryAttribute("DataBinding"), _
DescriptionAttribute("Imposta il nome del campo chiave relativo alle tabelle")> _
Public Property IDField() As String()
' Restituisce/Imposta il nome del campo ID
Get
Return StrIDField
End Get
Set(ByVal value As String())
StrIDField = value
End Set
End Property
<Bindable(True), CategoryAttribute("DataBinding"), _
DescriptionAttribute("Imposta il nome del campo descrittivo relativo alle tabelle")> _
Public Property DescriptionField() As String()
' Restituisce/Imposta il nome del campo Descrittivo
Get
Return StrFieldDescription
End Get
Set(ByVal value As String())
StrFieldDescription = value
End Set
End Property
<Bindable(True), CategoryAttribute("DataBinding"), _
DescriptionAttribute("Imposta il nome del campo padre relativo alle tabella a livello superiore")> _
Public Property ParentField() As String()
' Restituisce/Imposta il nome del campo Chiave in riferimento al compo ID
' della tabella padre
Get
Return StrFieldParent
End Get
Set(ByVal value As String())
StrFieldParent = value
End Set
End Property
<Bindable(True), CategoryAttribute("DataBinding"), _
DescriptionAttribute("Imposta il nome del campo padre relativo alle tabella a livello superiore")> _
Public Property GrandParentField() As String
' Restituisce/Imposta il nome del campo Chiave in riferimento al compo ID
' della tabella padre
Get
Return StrFieldGrandParent
End Get
Set(ByVal value As String)
StrFieldGrandParent = value
End Set
End Property
#End Region
#Region "Metodi di classe"
Public Sub MoveUp(ByVal Node As TreeNode)
' Sposta il nodo selezionato verso l'alto nel treeview
Dim NewIndex As Integer = 0
Dim NodeClone As TreeNode = Nothing
If node.Parent Is Nothing Then Exit Sub
Try
If node Is Nothing Then Return
If node.Index = 0 Then Return
NewIndex = node.Index - 1
NodeClone = CType(node.Clone(), TreeNode)
node.Parent.Nodes.Insert(NewIndex, NodeClone)
node.Parent.Nodes.Remove(node)
NodeClone.TreeView.SelectedNode = NodeClone
Catch ex As Exception
Throw New Exception("Errore nello spostamento verso l'alto del nodo")
End Try
End Sub
Public Sub MoveDown(ByVal Node As TreeNode)
' Sposta il nodo selezionato verso il basso nel treeview
Dim NewIndex As Integer = 0
Dim NodeClone As TreeNode = Nothing
If node.Parent Is Nothing Then Exit Sub
Try
If node Is Nothing Then Return
NewIndex = node.Index + 2
If NewIndex > node.Parent.Nodes.Count Then Return
NodeClone = CType(node.Clone(), TreeNode)
node.Parent.Nodes.Insert(NewIndex, NodeClone)
node.Parent.Nodes.Remove(node)
NodeClone.TreeView.SelectedNode = NodeClone
Catch ex As Exception
Throw New Exception("Errore nello spostamento verso il basso del nodo")
End Try
Return
End Sub
Public Sub DeleteNode(ByVal StateNode As TreeNode)
' Elimina il nodo selezionato
Try
If StateNode Is Nothing Then Return
' Scatena l'evento BeforeDeleteNode intercettabile per gestire un'eventuale
' annullamento dell'eliminazione
Dim IsCancelled As Boolean = False
Dim Action As TreeViewAction = TreeViewAction.Unknown
RaiseEvent BeforeDeleteNode(Me, New TreeViewCancelEventArgs(StateNode, IsCancelled, Action))
If IsCancelled Then Exit Sub
Dim row As DataRow = CType(StateNode.Tag, DataRow)
If Not IsNothing(row) Then
' Elimina tutti i nodi figli
Dim ParentNodes As TreeNodeCollection = StateNode.Nodes
For Each ParentNode As TreeNode In ParentNodes
Dim ChildNodes As TreeNodeCollection = ParentNode.Nodes
For Each ChildNode As TreeNode In ChildNodes
Dim Childrow As DataRow = CType(ChildNode.Tag, DataRow)
If Not IsNothing(Childrow) Then
Childrow.Delete()
End If
Next
Dim ParentRow As DataRow = CType(ParentNode.Tag, DataRow)
If Not IsNothing(ParentRow) Then
ParentRow.Delete()
ParentRow.Delete()
End If
Next
' Elimina il nodo padre
row = CType(StateNode.Tag, DataRow)
If Not IsNothing(row) Then
row.Delete()
End If
End If
StateNode.Remove()
Catch ex As Exception
'
End Try
Return
End Sub
Public Sub AddNode(ByVal Node As TreeNode)
Dim newNode As TreeNode = Nothing
Dim ParentRow As DataRow = Nothing
Dim dt As DataTable = Nothing
Dim dtAdd As DataTable = Nothing
Dim newindex As Integer = 0
Me.Cursor = Cursors.WaitCursor
Try
ParentRow = CType(Node.Tag, DataRow)
If ParentRow Is Nothing Then Return
dt = ParentRow.Table
' Controlla che non ci troviamo all'ultimo livello gerarchico
If dt.TableName = StrTableName(2) Then
MsgBox("Impossibile creare ulteriori livelli")
Exit Sub
End If
' Create la nuova riga nel DataTable
If dt.TableName = StrTableName(0) Then
dtAdd = Ds.Tables(StrTableName(1))
Dim CurNewRow As DataRow = dtAdd.Rows.Add
CurNewRow.Item(StrFieldParent(0)) = ParentRow.Item(StrIDField(0))
' Trova il primo valore key progressivo
Dim StrFiltro As String
StrFiltro = StrFieldParent(0) & "=" & ParentRow.Item(StrIDField(0))
CurNewRow.Item(StrFieldDescription(1)) = "Nuovo nodo"
CurNewRow.Item(StrIDField(1)) = TrovaNuovo(dtAdd, StrIDField(1), StrFiltro)
' Aggiunge il nodo
newNode = Node.Nodes.Add("Nuovo nodo")
newNode.Tag = CurNewRow
' Espande il nodo corrente
Node.Expand()
' Pone in modalità modifica il nuovo nodo creato
Me.SelectedNode = newNode
newNode.BeginEdit()
ElseIf dt.TableName = StrTableName(1) Then
dtAdd = Ds.Tables(StrTableName(2))
Dim CurNewRow As DataRow = dtAdd.Rows.Add
CurNewRow.Item(StrFieldGrandParent) = ParentRow.Item(StrFieldParent(0))
CurNewRow.Item(StrFieldParent(1)) = ParentRow.Item(StrIDField(1))
' Trova il primo valore key progressivo
Dim StrFiltro As String
StrFiltro = StrFieldGrandParent & "=" & ParentRow.Item(StrFieldParent(0)) & " AND " & StrFieldParent(1) & "=" & ParentRow.Item(StrIDField(1))
CurNewRow.Item(StrFieldDescription(2)) = "Nuovo nodo"
CurNewRow.Item(StrIDField(2)) = TrovaNuovo(dtAdd, StrIDField(2), StrFiltro)
' Aggiunge il nodo
newNode = Node.Nodes.Add("Nuovo nodo")
newNode.Tag = CurNewRow
' Espande il nodo corrente
Node.Expand()
' Pone in modalità modifica il nuovo nodo creato
Me.SelectedNode = newNode
newNode.BeginEdit()
End If
Catch ex As Exception
'
Finally
Me.Cursor = Cursors.Default
End Try
End Sub
Public Sub AddRootNode()
Dim newNode As TreeNode = Nothing
Dim ParentRow As DataRow = Nothing
Dim dt As DataTable = Nothing
Dim newindex As Integer = 0
Me.Cursor = Cursors.WaitCursor
Try
' Create la nuova riga nel DataTable
dt = Ds.Tables(StrTableName(0))
Dim CurNewRow As DataRow = dt.Rows.Add
' Trova il primo valore key progressivo
CurNewRow.Item(StrIDField(0)) = TrovaNuovo(dt, StrIDField(0), "")
CurNewRow.Item(StrFieldDescription(0)) = "Nuovo nodo"
' Aggiunge il nodo
newNode = Me.Nodes.Add("Nuovo nodo")
newNode.Tag = CurNewRow
' Pone in modalità modifica il nuovo nodo creato
Me.SelectedNode = newNode
newNode.BeginEdit()
Catch ex As Exception
'
Finally
Me.Cursor = Cursors.Default
End Try
End Sub
Public Sub ShowData()
' Carica i nodi principali
Call LoadRootNodes()
End Sub
#End Region
#Region "Routine di classe"
Public Sub ShowContextMenu(ByVal tv As TreeView, ByVal menu As ContextMenu)
' Mostra il menu contestuale
Try
Dim pt As New Point(tv.SelectedNode.Bounds.Left, tv.SelectedNode.Bounds.Bottom)
menu.Show(tv, pt)
Catch ex As Exception
Throw
End Try
End Sub
Private Sub LoadRootNodes()
' Carica i nodi principali dalla prima tabella del Dataset
Dim Dt As DataTable = Ds.Tables(StrTableName(0))
For Each CurRow As DataRow In Dt.Rows
Dim CurNode As TreeNode = Me.Nodes.Add(CurRow.Item(StrFieldDescription(0)))
CurNode.Tag = CurRow
LoadParentNodes(CurNode, CurRow.Item(StrIDField(0)))
Next
End Sub
Private Sub LoadParentNodes(ByVal CurNode As TreeNode, ByVal RootValue As Integer)
' Carica i nodi figli dalla seconda tabella del Dataset
Dim SelRows As DataRow()
SelRows = Ds.Tables(StrTableName(1)).Select(StrFieldParent(0) & "=" & RootValue)
For Each CurRow As DataRow In SelRows
Dim NewNode As TreeNode = CurNode.Nodes.Add(CurRow.Item(StrFieldDescription(1)))
NewNode.Tag = CurRow
LoadChildNodes(NewNode, RootValue, CurRow.Item(StrIDField(1)))
Next
End Sub
Private Sub LoadChildNodes(ByVal CurNode As TreeNode, ByVal RootValue As Integer, ByVal ParentValue As Integer)
' Carica i nodi figli dalla seconda tabella del Dataset
Dim SelRows As DataRow()
SelRows = Ds.Tables(StrTableName(2)).Select(StrFieldGrandParent & "=" & RootValue & " AND " & StrFieldParent(1) & "=" & ParentValue)
For Each CurRow As DataRow In SelRows
If Not IsDBNull(CurRow.Item(StrFieldDescription(2))) Then
Dim NewNode As TreeNode = CurNode.Nodes.Add(CurRow.Item(StrFieldDescription(2)))
NewNode.Tag = CurRow
End If
Next
End Sub
Public Sub New()
' Chiamata richiesta da Progettazione Windows Form.
InitializeComponent()
' Aggiungere le eventuali istruzioni di inizializzazione dopo la chiamata a InitializeComponent().
For IntCont As Integer = 0 To 2
StrTableName(IntCont) = ""
StrIDField(IntCont) = ""
StrFieldDescription(IntCont) = ""
Next
For IntCont As Integer = 0 To 1
StrFieldParent(IntCont) = ""
Next
StrFieldGrandParent = ""
' Inizializza il menu contestuale
InizializzaMenu()
End Sub
Private Sub InizializzaMenu()
' ctxMenu1
With CtxMenu
.Items.Add("Inserisci", Nothing, New EventHandler(AddressOf InserisciNuovoNodo))
.Items.Add("Modifica", Nothing, New EventHandler(AddressOf ModificaNodo))
.Items.Add("Sposta su", Nothing, New EventHandler(AddressOf SpostaSu))
.Items.Add("Sposta giù", Nothing, New EventHandler(AddressOf SpostaGiu))
.Items.Add("Elimina", Nothing, New EventHandler(AddressOf EliminaNodo))
End With
End Sub
Private Function TrovaNuovo(ByVal dt As DataTable, ByVal Key As String, ByVal StrFilter As String) As Integer
' Trova il primo codice key progressivo successivo
Dim Dv As New DataView
Dv.Table = dt
Dv.RowFilter = StrFilter
Dv.Sort = Key & " DESC"
Try
Return Integer.Parse(Dv.Item(0).Item(Key)) + 1
Catch ex As Exception
Return 1
End Try
End Function
#End Region
#Region "Eventi Context menu"
Private Sub InserisciNuovoNodo(ByVal sender As Object, ByVal e As EventArgs)
AddNode(Me.SelectedNode)
End Sub
Private Sub ModificaNodo(ByVal sender As Object, ByVal e As EventArgs)
Me.Cursor = Cursors.WaitCursor
Try
Dim node As TreeNode = Me.SelectedNode
If node Is Nothing Then Return
node.TreeView.LabelEdit = True
node.BeginEdit()
Catch ex As Exception
MsgBox(ex.Message)
Finally
Me.Cursor = Cursors.Default
End Try
End Sub
Private Sub SpostaSu(ByVal sender As Object, ByVal e As EventArgs)
Me.Cursor = Cursors.WaitCursor
Try
Dim node As TreeNode = Me.SelectedNode
Call MoveUp(node)
Catch ex As Exception
'
Finally
Me.Cursor = Cursors.Default
End Try
End Sub
Private Sub SpostaGiu(ByVal sender As Object, ByVal e As EventArgs)
Me.Cursor = Cursors.WaitCursor
Try
Dim node As TreeNode = Me.SelectedNode
Call MoveDown(node)
Catch ex As Exception
'
Finally
Me.Cursor = Cursors.Default
End Try
End Sub
Private Sub EliminaNodo(ByVal sender As Object, ByVal e As EventArgs)
Me.Cursor = Cursors.WaitCursor
Try
Dim node As TreeNode = Me.SelectedNode
Call DeleteNode(node)
Catch ex As Exception
'
Finally
Me.Cursor = Cursors.Default
End Try
End Sub
#End Region
#Region "Eventi"
Private Sub DataTree_AfterLabelEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.NodeLabelEditEventArgs) Handles Me.AfterLabelEdit
' Modifica il testo nel database
Dim Dt As DataTable = Nothing
Dim EditedRow As DataRow = Nothing
EditedRow = CType(e.Node.Tag, DataRow)
Dt = EditedRow.Table
If Dt.TableName = StrTableName(0) Then
If e.Label = "" Then
EditedRow.Item(StrFieldDescription(0)) = e.Node.Text
Else
EditedRow.Item(StrFieldDescription(0)) = e.Label
End If
ElseIf Dt.TableName = StrTableName(1) Then
If e.Label = "" Then
EditedRow.Item(StrFieldDescription(1)) = e.Node.Text
Else
EditedRow.Item(StrFieldDescription(1)) = e.Label
End If
ElseIf Dt.TableName = StrTableName(2) Then
If e.Label = "" Then
EditedRow.Item(StrFieldDescription(2)) = e.Node.Text
Else
EditedRow.Item(StrFieldDescription(2)) = e.Label
End If
End If
End Sub
Private Sub DataTree_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
If e.KeyCode = Keys.Delete Then
EliminaNodo(sender, e)
End If
End Sub
Private Sub DataTree_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
Select Case e.Button
Case Windows.Forms.MouseButtons.Right
CtxMenu.Show(Me, New Point(e.X, e.Y))
Return
Case Else
End Select
End Sub
#End Region
#Region "Eventi Pubblici"
Public Event BeforeDeleteNode(ByVal sender As Object, ByVal e As TreeViewCancelEventArgs)
#End Region
End Class