Imports System.Runtime.InteropServices
Imports System.Drawing
Public Class frmMain
'***********************************************************************************************
'*** Start code to hide the checkbox on root nodes. Set DrawMode to OwnerDrawAll.
'*** The treeview will not show in the designer. To edit the tree in the designer, temporarily
'*** set DrawMode to Normal, apply edits, and set DrawMode back to OwnerDrawAll.
'***********************************************************************************************
Private Const TVIF_STATE As Integer = &H8
Private Const TVIS_STATEIMAGEMASK As Integer = &HF000
Private Const TV_FIRST As Integer = &H1100
Private Const TVM_SETITEM As Integer = TV_FIRST + 63
<StructLayout(LayoutKind.Sequential)>
Public Structure TVITEM
Public mask As Integer
Public hItem As IntPtr
Public state As Integer
Public stateMask As Integer
<MarshalAs(UnmanagedType.LPTStr)> Public lpszText As String
Public cchTextMax As Integer
Public iImage As Integer
Public iSelectedImage As Integer
Public cChildren As Integer
Public lParam As IntPtr
End Structure
<DllImport("User32.dll", CharSet:=CharSet.Auto)>
Private Shared Function SendMessage(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByRef lParam As TVITEM) As Integer
End Function
Private Sub HideRootCheckBox(ByVal node As TreeNode)
Dim tvi As New TVITEM
tvi.hItem = node.Handle
tvi.mask = TVIF_STATE
tvi.stateMask = TVIS_STATEIMAGEMASK
tvi.state = 0
SendMessage(treeMain.Handle, TVM_SETITEM, IntPtr.Zero, tvi)
End Sub
Private Sub TreeView1_DrawNode(ByVal sender As Object, ByVal e As DrawTreeNodeEventArgs) Handles treeMain.DrawNode
' Check if the handle is created before trying to hide the checkbox
If e.Node.IsHandleCreated AndAlso e.Node.Parent Is Nothing Then
HideRootCheckBox(e.Node)
End If
e.DrawDefault = True
End Sub
'***********************************************************************************************
'*** End code to hide the checkbox on root nodes.
'***********************************************************************************************
Private Const COMMANDTITLE = "AAU with a tree"
Private Const DIFFVAL = "(Different Values)"
Private Const MSGLASTCHANCE = "You have unapplied updates. Do you want to save them?" ' Modified message
Private m_bMyForm As Boolean ' Flag used in AfterCheck and AfterCollapse to indicate programmatic changes
Private m_sActiveFeatureClass As String ' Tracks the currently active root node text (e.g., "States")
Private m_treeContextNode As TreeNode ' Used for context menu
' *** New Flags for handling unsaved changes and BeforeSelect re-entrancy ***
Private changesMade As Boolean = False
Private isHandlingBeforeSelect As Boolean = False
' *************************************************************************
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
m_bMyForm = False
m_sActiveFeatureClass = ""
m_treeContextNode = Nothing
' Hook up event handler for DataGridView changes
' In a real application, you would hook this up to an event
' that indicates a true data change, like CellValueChanged or CellValidated
' For this example, we'll add a simple way to simulate changes later if needed.
' AddHandler dgvMain.CellValueChanged, AddressOf dgvMain_CellValueChanged ' Example
End Sub
' Example handler for DataGridView changes (uncomment and implement based on your grid's data source)
' Private Sub dgvMain_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs)
' ' Check if the change is in a data row (not header or new row row)
' If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then
' changesMade = True
' End If
' End Sub
Private Sub frmMain_Activated(sender As Object, e As EventArgs) Handles Me.Activated
' Optionally clear selection when form is activated if no node should be selected initially
' treeMain.SelectedNode = Nothing
End Sub
Private Sub btnReset_Click(sender As Object, e As EventArgs) Handles btnReset.Click
' Consider prompting to save changes before resetting
If changesMade Then
Dim result As DialogResult = MessageBox.Show("You have unsaved changes. Do you want to save them before resetting?", "Save Changes?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning)
If result = DialogResult.Yes Then
SaveDataGridViewChanges() ' Call your save method
End If
' If No is clicked, changesMade is already True, and ResetForm will set it to False
End If
Try
ResetForm()
Catch ex As Exception
' Handle exception
Finally
' Cleanup or final actions
End Try
End Sub
Private Sub treeMain_AfterCheck(sender As Object, e As TreeViewEventArgs) Handles treeMain.AfterCheck
' This event should only fire when a child node is checked/unchecked.
' m_bMyForm is used to prevent this handler from firing when checkboxes
' are programmatically changed in AfterCollapse.
If m_bMyForm = False Then
If e.Node.Parent IsNot Nothing Then ' Make sure it's a child node
If e.Node.Checked Then
' If a child node is checked, load its data
' Consider if checking a child node should also trigger a save prompt
' if a different child node from the same root had unsaved changes.
' For now, we'll assume unsaved changes are tied to the root node selection.
PopGrid(e.Node.Text)
m_sActiveFeatureClass = e.Node.Parent.Text.Substring(0, InStr(e.Node.Parent.Text, " (") - 1) ' Update active feature class based on parent
changesMade = False ' Assume loading new data clears changes for the previous child data
Else
' If unchecked, clear the grid if this was the active data
' Consider if unchecking should also prompt for save if there are changes
If m_sActiveFeatureClass <> "" AndAlso e.Node.Text = m_sActiveFeatureClass Then ' This logic might need refinement depending on how you identify the active child data
dgvMain.RowCount = 0
m_sActiveFeatureClass = ""
changesMade = False ' Clearing the grid implies discarding changes
End If
End If
End If
End If
End Sub
Private Sub treeMain_AfterCollapse(sender As Object, e As TreeViewEventArgs) Handles treeMain.AfterCollapse
' After a collapse, we don't want checked child nodes visually.
Dim iNumChecked As Integer = 0
Try
' Set flag to indicate programmatic changes to checkboxes
m_bMyForm = True
For Each childNode As TreeNode In e.Node.Nodes
If childNode.Checked Then
childNode.Checked = False
iNumChecked = iNumChecked + 1
End If
Next
' Reset flag
m_bMyForm = False
' Consider if collapsing should also trigger a save prompt if children had unsaved changes.
' The current logic just shows a message box if any were checked.
' If you want to prompt to save here, you would check 'changesMade' and prompt.
' If iNumChecked > 0 AndAlso changesMade Then
' Dim result As DialogResult = MessageBox.Show(MSGLASTCHANCE, "Save Changes?", MessageBoxButtons.YesNo, MsgBoxStyle.Exclamation, COMMANDTITLE)
' If result = DialogResult.Yes Then
' SaveDataGridViewChanges() ' Call your save method
' End If
' changesMade = False ' Discard or saved
' End If
' Clear grid and active state if the collapsed node's feature class was active
Dim collapsedFeatureClass As String = ""
If e.Node.Text.Contains(" (") Then
collapsedFeatureClass = e.Node.Text.Substring(0, InStr(e.Node.Text, " (") - 1)
End If
If collapsedFeatureClass <> "" AndAlso collapsedFeatureClass = m_sActiveFeatureClass Then
dgvMain.RowCount = 0
m_sActiveFeatureClass = ""
changesMade = False ' Clearing the grid implies discarding changes
End If
Catch ex As Exception
' Handle exception
Finally
' Ensure flag is reset even if an error occurs
m_bMyForm = False
End Try
End Sub
Private Sub treeMain_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles treeMain.AfterSelect
' This event fires after a node has been successfully selected (After BeforeSelect).
' Load the grid data for the selected node here.
' The save prompt logic is handled in BeforeSelect.
Try
If e.Node.Parent Is Nothing Then ' Only load for root nodes as per your original logic
Dim sFeatureClass As String = ""
If e.Node.Text.Contains(" (") Then
sFeatureClass = e.Node.Text.Substring(0, InStr(e.Node.Text, " (") - 1)
End If
' Update the active feature class after a successful selection
m_sActiveFeatureClass = sFeatureClass
' Load the grid data for the newly selected root node
PopGrid(e.Node.Text)
' PopGrid sets changesMade = False internally
Else
' For child nodes, selection is cancelled in BeforeSelect,
' and data loading is handled in AfterCheck if they are checked.
' So, no action needed here for child nodes if your design keeps them unhighlighted.
End If
Catch ex As Exception
' Handle exception
Finally
' Cleanup or final actions
End Try
End Sub
Private Sub treeMain_BeforeSelect(sender As Object, e As TreeViewCancelEventArgs) Handles treeMain.BeforeSelect
' This event fires before a new node is selected.
' This is the place to check for unsaved changes and prompt the user.
' Prevent re-entrancy caused by showing the message box
If isHandlingBeforeSelect Then
Return
End If
' Set the flag to indicate we are handling the BeforeSelect event
isHandlingBeforeSelect = True
Dim treeNode As TreeNode = e.Node ' The node the user is trying to select
Dim currentNode As TreeNode = treeMain.SelectedNode ' The node that is currently selected
Try
' Check if there are unsaved changes and the user is selecting a *different* root node
If changesMade AndAlso currentNode IsNot Nothing AndAlso currentNode.Parent Is Nothing AndAlso treeNode.Parent Is Nothing AndAlso treeNode IsNot currentNode Then
' Display the save changes message box
Dim result As DialogResult = MessageBox.Show(MSGLASTCHANCE, "Save Changes?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning)
Select Case result
Case DialogResult.Yes
' User wants to save changes.
SaveDataGridViewChanges() ' Call your save method
' Allow the new node selection to proceed.
e.Cancel = False
' AfterSelect will handle loading data for the new node and setting changesMade = False.
Case DialogResult.No
' User does not want to save changes.
' Discard changes by resetting the flag.
changesMade = False
' Allow the new node selection to proceed.
e.Cancel = False
' AfterSelect will handle loading data for the new node and setting changesMade = False.
Case DialogResult.Cancel
' User cancelled the selection change.
' Prevent the new node from being selected.
e.Cancel = True
' Re-select the previously selected node to visually revert the selection.
' Temporarily remove the event handler to prevent this programmatic selection
' from re-triggering BeforeSelect and causing a loop.
RemoveHandler treeMain.BeforeSelect, AddressOf treeMain_BeforeSelect
' Restore the original node selection
If currentNode IsNot Nothing Then
treeMain.SelectedNode = currentNode
Else
' If there was no previous selection (e.g., initial load),
' you might want to select a default node or keep none selected.
' For now, we'll just ensure nothing is selected if there was no previous node.
' If treeMain.Nodes.Count > 0 Then
' treeMain.SelectedNode = treeMain.Nodes(0)
' End If
End If
' Re-add the event handler
AddHandler treeMain.BeforeSelect, AddressOf treeMain_BeforeSelect
End Select
ElseIf treeNode.Parent IsNot Nothing Then ' Selected a child node
' Your existing logic for child nodes (check and cancel selection)
e.Node.Checked = True
e.Cancel = True
' AfterCheck will handle loading data for the checked child node and setting changesMade = False.
ElseIf treeNode Is currentNode Then ' Selected the already selected root node
' Cancel the selection if the user clicks the same root node again
e.Cancel = True
Else
' No unsaved changes, or selecting the first root node.
' Allow the selection to proceed.
e.Cancel = False
' AfterSelect will handle loading data for the new node and setting changesMade = False.
End If
Catch ex As Exception
' Handle exception
Finally
' Ensure the flag is reset when the handler exits, regardless of how it exits.
isHandlingBeforeSelect = False
End Try
End Sub
Private Sub treeMain_NodeMouseClick(sender As Object, e As TreeNodeMouseClickEventArgs) Handles treeMain.NodeMouseClick
' Your existing context menu logic
Try
If e.Button = MouseButtons.Right Then
m_treeContextNode = e.Node
' Show your context menu here, e.g.:
' Me.contextMenuStrip1.Show(treeMain, e.Location)
End If
Catch ex As Exception
' Handle exception
Finally
' Cleanup
End Try
End Sub
Private Sub mnuTree1Remove_Click(sender As Object, e As EventArgs) Handles mnuTree1Remove.Click
' Your existing node removal logic
Try
If m_treeContextNode IsNot Nothing Then
' Before removing, check if the node's data is currently loaded and has changes
Dim featureClassToRemove As String = ""
If m_treeContextNode.Parent Is Nothing Then ' Root node
If m_treeContextNode.Text.Contains(" (") Then
featureClassToRemove = m_treeContextNode.Text.Substring(0, InStr(m_treeContextNode.Text, " (") - 1)
End If
Else ' Child node
featureClassToRemove = m_treeContextNode.Text
End If
If changesMade AndAlso m_sActiveFeatureClass <> "" AndAlso (
(m_treeContextNode.Parent Is Nothing AndAlso featureClassToRemove = m_sActiveFeatureClass) OrElse
(m_treeContextNode.Parent IsNot Nothing AndAlso m_treeContextNode.Text = m_sActiveFeatureClass)) Then ' This comparison logic for child nodes might need adjustment based on how you set m_sActiveFeatureClass for children
Dim result As DialogResult = MessageBox.Show("You have unsaved changes for the item being removed. Do you want to save them?", "Save Changes?", MessageBoxButtons.YesNo, MsgBoxIcon.Warning)
If result = DialogResult.Yes Then
SaveDataGridViewChanges() ' Call your save method
End If
' If No is clicked, changes are discarded with the removal
End If
' Remove the node
Dim treeParent As TreeNode = m_treeContextNode.Parent
m_treeContextNode.Remove()
' Update parent node text if it was a child
If treeParent IsNot Nothing Then
If treeParent.Nodes.Count = 0 Then
treeParent.Remove()
Else
Dim sName As String = treeParent.Text
Dim iNdx As Integer = InStr(sName, " (")
sName = sName.Substring(0, iNdx)
sName = sName & " (" & treeParent.Nodes.Count & ")"
treeParent.Text = sName
End If
End If
m_treeContextNode = Nothing
' After removal, clear the grid and active state if the removed node was active
If featureClassToRemove <> "" AndAlso featureClassToRemove = m_sActiveFeatureClass Then
dgvMain.RowCount = 0
m_sActiveFeatureClass = ""
changesMade = False ' Discarding data
End If
End If
Catch ex As Exception
' Handle exception
Finally
' Cleanup
End Try
End Sub
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles Me.Load
ResetForm()
End Sub
' Removed the separate LastChance() method as its logic is now integrated into BeforeSelect
Private Sub PopGrid(ByVal sSelectedNodeText As String)
' This is a hardcoded function to simulate populating the grid when a node is selected.
' In a real application, you would fetch data based on the node and bind it to the DGV.
Try
dgvMain.RowCount = 0
dgvMain.ColumnCount = 2 ' Assuming a simple key/value structure for now
' Set column headers
dgvMain.Columns(0).HeaderText = "Property"
dgvMain.Columns(1).HeaderText = "Value"
dgvMain.Columns(0).ReadOnly = True ' Make property column read-only
dgvMain.Columns(1).ReadOnly = False ' Allow editing in value column
' Adjust column widths
dgvMain.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells)
' --- Simulate loading data based on node text ---
If sSelectedNodeText.StartsWith("States (") Then
dgvMain.RowCount = 5
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = DIFFVAL
dgvMain(0, 1).Value = "FIPS" : dgvMain(1, 1).Value = DIFFVAL
dgvMain(0, 2).Value = "STATE_NAME" : dgvMain(1, 2).Value = DIFFVAL
dgvMain(0, 3).Value = "STAABBRV" : dgvMain(1, 3).Value = DIFFVAL
dgvMain(0, 4).Value = "Test1" : dgvMain(1, 4).Value = "123"
ElseIf sSelectedNodeText.StartsWith("Rivers (") Then
dgvMain.RowCount = 3
dgvMain(0, 0).Value = "RIVER_NAME" : dgvMain(1, 0).Value = DIFFVAL
dgvMain(0, 1).Value = "RIVER_TYPE" : dgvMain(1, 1).Value = "Perennial - Single Line"
dgvMain(0, 2).Value = "ID" : dgvMain(1, 2).Value = DIFFVAL
ElseIf sSelectedNodeText.StartsWith("Interstates (") Then
dgvMain.RowCount = 3
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = DIFFVAL
dgvMain(0, 1).Value = "ROUTE_NUMBER" : dgvMain(1, 1).Value = DIFFVAL
dgvMain(0, 2).Value = "ROUTE_NUMBER2" : dgvMain(1, 2).Value = "<Null>"
ElseIf sSelectedNodeText = "Alabama" Then
dgvMain.RowCount = 5
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = "1"
dgvMain(0, 1).Value = "FIPS" : dgvMain(1, 1).Value = "01"
dgvMain(0, 2).Value = "STATE_NAME" : dgvMain(1, 2).Value = "Alabama"
dgvMain(0, 3).Value = "STAABBRV" : dgvMain(1, 3).Value = "AL"
dgvMain(0, 4).Value = "Test1" : dgvMain(1, 4).Value = "123"
ElseIf sSelectedNodeText = "Georgia" Then
dgvMain.RowCount = 5
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = "10"
dgvMain(0, 1).Value = "FIPS" : dgvMain(1, 1).Value = "13"
dgvMain(0, 2).Value = "STATE_NAME" : dgvMain(1, 2).Value = "Georgia"
dgvMain(0, 3).Value = "STAABBRV" : dgvMain(1, 3).Value = "GA"
dgvMain(0, 4).Value = "Test1" : dgvMain(1, 4).Value = "123"
ElseIf sSelectedNodeText = "Tennessee" AndAlso m_sActiveFeatureClass = "States" Then
dgvMain.RowCount = 5
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = "41"
dgvMain(0, 1).Value = "FIPS" : dgvMain(1, 1).Value = "47"
dgvMain(0, 2).Value = "STATE_NAME" : dgvMain(1, 2).Value = "Tennessee"
dgvMain(0, 3).Value = "STAABBRV" : dgvMain(1, 3).Value = "TN"
dgvMain(0, 4).Value = "Test1" : dgvMain(1, 4).Value = "123"
ElseIf sSelectedNodeText = "Tennessee" AndAlso m_sActiveFeatureClass = "Rivers" Then
dgvMain.RowCount = 3
dgvMain(0, 0).Value = "RIVER_NAME" : dgvMain(1, 0).Value = "Tennessee"
dgvMain(0, 1).Value = "RIVER_TYPE" : dgvMain(1, 1).Value = "Perennial - Single Line"
dgvMain(0, 2).Value = "ID" : dgvMain(1, 2).Value = "50"
ElseIf sSelectedNodeText = "Tombigbee" Then
dgvMain.RowCount = 3
dgvMain(0, 0).Value = "RIVER_NAME" : dgvMain(1, 0).Value = "Tombigbee"
dgvMain(0, 1).Value = "RIVER_TYPE" : dgvMain(1, 1).Value = "Perennial - Single Line"
dgvMain(0, 2).Value = "ID" : dgvMain(1, 2).Value = "90"
ElseIf sSelectedNodeText = "65" Then
dgvMain.RowCount = 3
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = "533"
dgvMain(0, 1).Value = "ROUTE_NUMBER" : dgvMain(1, 1).Value = "65"
dgvMain(0, 2).Value = "ROUTE_NUMBER2" : dgvGrid(1, 2).Value = "<Null>"
ElseIf sSelectedNodeText = "565" Then
dgvMain.RowCount = 3
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = "532"
dgvMain(0, 1).Value = "ROUTE_NUMBER" : dgvMain(1, 1).Value = "565"
dgvMain(0, 2).Value = "ROUTE_NUMBER2" : dgvMain(1, 2).Value = "<Null>"
ElseIf sSelectedNodeText = "20" Then
dgvMain.RowCount = 3
dgvMain(0, 0).Value = "ID" : dgvMain(1, 0).Value = "474"
dgvMain(0, 1).Value = "ROUTE_NUMBER" : dgvMain(1, 1).Value = "20"
dgvMain(0, 2).Value = "ROUTE_NUMBER2" : dgvMain(1, 2).Value = "<Null>"
End If
' Apply specific styles after populating the data
For i As Integer = 0 To dgvMain.RowCount - 1
If dgvMain(0, i).Value IsNot Nothing AndAlso dgvMain(0, i).Value.ToString().EndsWith("ID") Then
dgvMain(0, i).Style.Font = New Font(dgvMain.DefaultCellStyle.Font, FontStyle.Bold Or FontStyle.Underline)
dgvMain(0, i).Style.ForeColor = Color.Red
' Make the value cell for ID read-only and gray
If dgvMain.ColumnCount > 1 Then
dgvMain(1, i).Style.BackColor = Color.LightGray
dgvMain(1, i).ReadOnly = True
End If
ElseIf dgvMain(0, i).Value IsNot Nothing AndAlso (dgvMain(0, i).Value.ToString().EndsWith("NAME") OrElse dgvMain(0, i).Value.ToString().EndsWith("NUMBER")) Then
dgvMain(0, i).Style.Font = New Font(dgvMain.DefaultCellStyle.Font, FontStyle.Bold)
dgvMain(0, i).Style.ForeColor = Color.Red
End If
' Make cells with DIFFVAL read-only
If dgvMain.ColumnCount > 1 AndAlso dgvMain(1, i).Value IsNot Nothing AndAlso dgvMain(1, i).Value.ToString() = DIFFVAL Then
dgvMain(1, i).Style.BackColor = Color.LightGray
dgvMain(1, i).ReadOnly = True
End If
Next
dgvMain.ClearSelection()
' Reset the changesMade flag after loading new data
changesMade = False
Catch ex As Exception
' Handle exception
Finally
' Cleanup
End Try
End Sub
Private Sub ResetForm()
'This function resets the form to its initial state.
Try
treeMain.Nodes.Clear()
Dim treeNode As New TreeNode("States (3)")
treeMain.Nodes.Add(treeNode)
treeNode.Nodes.Add("Alabama")
treeNode.Nodes.Add("Tennessee")
treeNode.Nodes.Add("Georgia")
treeNode = New TreeNode("Rivers (2)")
treeMain.Nodes.Add(treeNode)
treeNode.Nodes.Add("Tennessee")
treeNode.Nodes.Add("Tombigbee")
treeNode = New TreeNode("Interstates (3)")
treeMain.Nodes.Add(treeNode)
treeNode.Nodes.Add("65")
treeNode.Nodes.Add("565")
treeNode.Nodes.Add("20")
' treeMain.Sort() 'maybe later
' Clear the grid and reset state
dgvMain.RowCount = 0
m_sActiveFeatureClass = ""
changesMade = False
' Ensure no node is selected initially
treeMain.SelectedNode = Nothing
Catch ex As Exception
' Handle exception
Finally
' Cleanup
End Try
End Sub
' Add this placeholder method for your saving logic
Private Sub SaveDataGridViewChanges()
' *** IMPORTANT: Implement your actual data saving logic here. ***
' This method should take the data from the DataGridView and persist it
' to your underlying data source (database, file, etc.).
MessageBox.Show("Saving changes...", "Save", MessageBoxButtons.OK, MessageBoxIcon.Information) ' Placeholder message
' After successfully saving, reset the changesMade flag
changesMade = False
End Sub
' You'll need to add handlers to set changesMade = True when the user
' edits the DataGridView. A common place is the CellValueChanged event
' if you're manually managing data, or if using data binding, events
' on the BindingSource or DataTable.
' Example (uncomment and implement):
' Private Sub dgvMain_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgvMain.CellValueChanged
' If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then ' Ensure it's a data cell
' changesMade = True
' End If
' End Sub
End Class