Files
Emmanuele Sassi ab3fe0ee70 EgwWPFBaseLib 3.1.3.6:
- aggiunta verifica che dimensione ViewPort non sia Infinity in EgwDataGrid
- eliminata colonna frozen a sinistra di default in EgwRightFrozenDataGrid
2026-03-16 12:12:13 +01:00

849 lines
38 KiB
VB.net

' Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
'
' Step 1a) Using this custom control in a XAML file that exists in the current project.
' Add this XmlNamespace attribute to the root element of the markup file where it is
' to be used:
'
' xmlns:MyNamespace="clr-namespace:WpfApp23"
'
'
' Step 1b) Using this custom control in a XAML file that exists in a different project.
' Add this XmlNamespace attribute to the root element of the markup file where it is
' to be used:
'
' xmlns:MyNamespace="clr-namespace:WpfApp23;assembly=WpfApp23"
'
' You will also need to add a project reference from the project where the XAML file lives
' to this project and Rebuild to avoid compilation errors:
'
' Right click on the target project in the Solution Explorer and
' "Add Reference"->"Projects"->[Browse to and select this project]
'
'
' Step 2)
' Go ahead and use your control in the XAML file. Note that Intellisense in the
' XML editor does not currently work on custom controls and its child elements.
'
' <MyNamespace:EgtDataGrid/>
'
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.ComponentModel
Imports System.IO
Imports System.Windows.Controls.Primitives
Imports System.Windows.Threading
Imports Newtonsoft.Json
Public Class EgwDataGrid
Inherits DataGrid
Public Property IsTableLocked As Boolean
Get
Return CBool(GetValue(IsTableLockedProperty))
End Get
Set(ByVal value As Boolean)
SetValue(IsTableLockedProperty, value)
End Set
End Property
Public Shared ReadOnly IsTableLockedProperty As DependencyProperty =
DependencyProperty.Register(NameOf(IsTableLocked), GetType(Boolean), GetType(EgwDataGrid),
New PropertyMetadata(False, AddressOf OnTableLockedChanged))
Public Property ColumnLayouts As ObservableCollection(Of ColumnLayout)
Get
Return CType(GetValue(ColumnLayoutsProperty), ObservableCollection(Of ColumnLayout))
End Get
Set(value As ObservableCollection(Of ColumnLayout))
SetValue(ColumnLayoutsProperty, value)
End Set
End Property
Public Shared ReadOnly ColumnLayoutsProperty As DependencyProperty =
DependencyProperty.Register(NameOf(ColumnLayouts), GetType(ObservableCollection(Of ColumnLayout)),
GetType(EgwDataGrid), New PropertyMetadata(Nothing, AddressOf OnColumnLayoutsChanged))
Private Shared Sub OnColumnLayoutsChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
If TypeOf d IsNot EgwDataGrid Or TypeOf e.NewValue IsNot ObservableCollection(Of ColumnLayout) Then Return
Dim grid As EgwDataGrid = DirectCast(d, EgwDataGrid)
Dim layouts As ObservableCollection(Of ColumnLayout) = DirectCast(e.NewValue, ObservableCollection(Of ColumnLayout))
AddHandler layouts.CollectionChanged, AddressOf grid.Layouts_CollectionChanged
Dim ToBeRemoved As New List(Of ColumnLayout)
For Each layout In layouts
If Not grid.AddColumnFromLayout(layout) Then
ToBeRemoved.Add(layout)
End If
Next
For nToBeRemovedIndex = ToBeRemoved.Count - 1 To 0 Step -1
layouts.Remove(ToBeRemoved(nToBeRemovedIndex))
Next
End Sub
Private Shared Sub OnTableLockedChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
If TypeOf d Is EgwDataGrid AndAlso TypeOf e.NewValue Is Boolean Then
Dim grid As EgwDataGrid = DirectCast(d, EgwDataGrid)
Dim locked As Boolean = DirectCast(e.NewValue, Boolean)
grid.ApplyLockToAllColumns(locked)
grid.RefreshAllHeaderMenus()
End If
End Sub
Public Shared ReadOnly LockAutoColumnProperty As DependencyProperty =
DependencyProperty.Register(NameOf(LockAutoColumn), GetType(Boolean), GetType(EgwDataGrid),
New PropertyMetadata(True))
Public Property LockAutoColumn As Boolean
Get
Return CBool(GetValue(LockAutoColumnProperty))
End Get
Set(ByVal value As Boolean)
SetValue(LockAutoColumnProperty, value)
End Set
End Property
Public Shared ReadOnly HeaderContextMenuStyleProperty As DependencyProperty =
DependencyProperty.Register(NameOf(HeaderContextMenuStyle), GetType(Style), GetType(EgwDataGrid), New PropertyMetadata(Nothing))
Public Property HeaderContextMenuStyle As Style
Get
Return CType(GetValue(HeaderContextMenuStyleProperty), Style)
End Get
Set(value As Style)
SetValue(HeaderContextMenuStyleProperty, value)
End Set
End Property
Public Shared ReadOnly HeaderContextMenuItemStyleProperty As DependencyProperty =
DependencyProperty.Register(NameOf(HeaderContextMenuItemStyle), GetType(Style), GetType(EgwDataGrid), New PropertyMetadata(Nothing))
Public Property HeaderContextMenuItemStyle As Style
Get
Return CType(GetValue(HeaderContextMenuItemStyleProperty), Style)
End Get
Set(value As Style)
SetValue(HeaderContextMenuItemStyleProperty, value)
End Set
End Property
Private ReadOnly _columnToLayout As New Dictionary(Of DataGridColumn, ColumnLayout)
Private _headersCaptured As Boolean = False
Private _initialSortingApplied As Boolean = False
Sub New()
Me.AutoGenerateColumns = False
AddHandler Me.Loaded, AddressOf OnDataGridLoaded
AddHandler Me.SizeChanged, AddressOf OnGridSizeChanged
AddHandler Columns.CollectionChanged, AddressOf OnColumnsChanged
End Sub
Private Sub OnDataGridLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
TryHookPresenter()
' aggiorno sorting all'avvio
If _initialSortingApplied Then Exit Sub
_initialSortingApplied = True
' Applica sorting dopo che il DataGrid ha completato il layout
Dispatcher.BeginInvoke(Sub()
ApplyInitialSorting()
End Sub, DispatcherPriority.Render)
End Sub
Private Sub OnGridSizeChanged(sender As Object, e As SizeChangedEventArgs)
Dispatcher.BeginInvoke(Sub() AdjustLastColumnFillMode(), DispatcherPriority.Loaded)
End Sub
Private Sub TryHookPresenter()
Dim presenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(Me)
If presenter Is Nothing Then Return
AddHandler presenter.ItemContainerGenerator.StatusChanged, AddressOf OnHeadersGenerated
End Sub
' da verificare che funzioni sempre
Private Sub OnHeadersGenerated(sender As Object, e As EventArgs)
If _headersCaptured Then Return
Dim presenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(Me)
If presenter Is Nothing Then Return
If presenter.ItemContainerGenerator.Status = GeneratorStatus.ContainersGenerated Then
' NON catturare subito: aspetta il layout completo
Dispatcher.BeginInvoke(
Sub()
CaptureAllHeaders()
End Sub,
DispatcherPriority.Loaded
)
End If
End Sub
Private Sub CaptureAllHeaders()
If _headersCaptured Then Return
Dim presenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(Me)
If presenter Is Nothing Then Return
_headersCaptured = True
RemoveHandler presenter.ItemContainerGenerator.StatusChanged, AddressOf OnHeadersGenerated
For i As Integer = 0 To Columns.Count - 1
If Columns(i).Visibility = Visibility.Visible Then
Dim header = TryCast(presenter.ItemContainerGenerator.ContainerFromIndex(i), DataGridColumnHeader)
If header IsNot Nothing Then
OnColumnHeaderCreated(header)
End If
End If
Next
End Sub
' li crea correttamente ma solo fino alla prima colonna nascosta
'Private Sub OnHeadersGenerated(ByVal sender As Object, ByVal e As EventArgs)
' If _headersCaptured Then Return
' Dim presenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(Me)
' If presenter Is Nothing Then Return
' If presenter.ItemContainerGenerator.Status = GeneratorStatus.ContainersGenerated Then
' _headersCaptured = True
' RemoveHandler presenter.ItemContainerGenerator.StatusChanged, AddressOf OnHeadersGenerated
' For i As Integer = 0 To Columns.Count - 1
' Dim header = TryCast(presenter.ItemContainerGenerator.ContainerFromIndex(i), DataGridColumnHeader)
' If header IsNot Nothing Then
' OnColumnHeaderCreated(header)
' End If
' Next
' End If
'End Sub
Protected Overridable Sub OnColumnHeaderCreated(ByVal header As DataGridColumnHeader)
If _columnToLayout.ContainsKey(header.Column) Then
GenerateHeaderEvents(header.Column, _columnToLayout(header.Column))
End If
End Sub
Private Sub Layouts_CollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
Select Case e.Action
Case NotifyCollectionChangedAction.Add
For Each layout As ColumnLayout In e.NewItems
AddColumnFromLayout(layout)
Next
Case NotifyCollectionChangedAction.Remove
For Each layout As ColumnLayout In e.OldItems
RemoveColumnFromLayout(layout)
Next
Case NotifyCollectionChangedAction.Replace
For Each oldLayout As ColumnLayout In e.OldItems
RemoveColumnFromLayout(oldLayout)
Next
For Each newLayout As ColumnLayout In e.NewItems
AddColumnFromLayout(newLayout)
Next
Case NotifyCollectionChangedAction.Reset
' Rimuove tutte le colonne esistenti usando RemoveColumnFromLayout
Dim layoutsToRemove = _columnToLayout.Values.ToList()
For Each layout In layoutsToRemove
RemoveColumnFromLayout(layout)
Next
' Ricostruisce le colonne da ColumnLayouts
For Each layout In ColumnLayouts
AddColumnFromLayout(layout)
Next
UpdateDisplayIndexes()
' faccio aggiornare layout perche' a volte dopo sovrascrittura lista non lo aggiorna in automatico
Me.UpdateLayout()
End Select
End Sub
Private Sub OnColumnsChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
_headersCaptured = False
TryHookPresenter()
End Sub
Private Function AddColumnFromLayout(layout As ColumnLayout) As Boolean
If Resources.Contains(layout.Key) Then
Dim baseColumn As DataGridColumn = TryCast(Resources(layout.Key), DataGridColumn)
If Not IsNothing(baseColumn) Then
Dim column As DataGridColumn = CloneColumn(baseColumn)
ApplyLayout(column, layout)
Columns.Add(column)
_columnToLayout(column) = layout
AddHandler layout.PropertyChanged, Sub(sender, args)
ApplyLayout(column, layout)
If (args.PropertyName = NameOf(ColumnLayout.IsVisible) AndAlso layout.IsVisible) Then
GenerateHeaderEvents(column, layout)
End If
End Sub
If IsTableLocked Then
layout.CanUserResize = False
layout.CanUserReorder = False
layout.CanUserSort = False
layout.IsReadOnly = True
End If
AddHandler Me.ColumnReordered, Sub(s, args)
Dim l = Nothing
If _columnToLayout.TryGetValue(args.Column, l) Then
Dim oldIndex = ColumnLayouts.IndexOf(l)
ColumnLayouts.Move(oldIndex, args.Column.DisplayIndex)
End If
End Sub
AddHandler Me.Sorting, Sub(s, args)
Dispatcher.BeginInvoke(New Action(Sub()
' Per ogni colonna del DataGrid
For Each col In Me.Columns
Dim sortLayout As ColumnLayout = Nothing
' Se esiste un ColumnLayout associato
If _columnToLayout.TryGetValue(col, sortLayout) Then
' Copia la SortDirection reale della colonna
sortLayout.SortDirection = col.SortDirection
End If
Next
End Sub), System.Windows.Threading.DispatcherPriority.Background)
End Sub
UpdateDisplayIndexes()
' Funzione che aggancia il listener alla SortDirection della colonna per aggiornare il valore quando cambiato manualmente in BindableFrozenDataGrid
Dim dpd = DependencyPropertyDescriptor.FromProperty(DataGridColumn.SortDirectionProperty, GetType(DataGridColumn))
dpd.AddValueChanged(column, AddressOf OnColumnSortDirectionChanged)
End If
Return True
End If
MessageBox.Show("Errore! Tentativo di caricare una colonna non definita!" & Environment.NewLine & " Column Key: " & layout.Key, "Errore!", MessageBoxButton.OK, MessageBoxImage.Error)
Return False
End Function
Private Sub OnColumnSortDirectionChanged(sender As Object, e As EventArgs)
Dim view = CollectionViewSource.GetDefaultView(Me.ItemsSource)
Dim col = TryCast(sender, DataGridColumn)
If col Is Nothing Then Return
If _columnToLayout.ContainsKey(col) Then
Dim layout = _columnToLayout(col)
layout.SortDirection = col.SortDirection
End If
End Sub
Private Sub ApplyInitialSorting()
Dim view = CollectionViewSource.GetDefaultView(Me.ItemsSource)
If view Is Nothing Then Exit Sub
' Cerca tutte le colonne con SortDirection nel ColumnLayout
For Each kvp In _columnToLayout
Dim col = kvp.Key
Dim layout = kvp.Value
If layout.SortDirection IsNot Nothing AndAlso
Not String.IsNullOrEmpty(col.SortMemberPath) Then
view.SortDescriptions.Add(
New SortDescription(col.SortMemberPath, layout.SortDirection)
)
End If
Next
view.Refresh()
End Sub
Private Sub GenerateHeaderEvents(column As DataGridColumn, layout As ColumnLayout)
Dispatcher.BeginInvoke(New Action(Sub()
Dim header = FindHeaderForColumn(column)
If header IsNot Nothing Then
header.ContextMenu = CreateHeaderContextMenu(layout)
AddHandler header.SizeChanged, Sub(s, e)
' Blocca il resize solo se LockAutoColumn è attivo e il layout è Auto
If LockAutoColumn AndAlso layout.WidthUnitType = DataGridLengthUnitType.Auto Then Return
' Se il layout è Auto ma LockAutoColumn è disattivato, converti in Pixel
If layout.WidthUnitType = DataGridLengthUnitType.Auto Then
column.Width = New DataGridLength(column.ActualWidth, DataGridLengthUnitType.Pixel)
layout.WidthUnitType = DataGridLengthUnitType.Pixel
End If
layout.WidthValue = column.ActualWidth
AdjustLastColumnFillMode()
End Sub
End If
End Sub), DispatcherPriority.Loaded)
End Sub
' funziona correttamente calcolando il valore giusto tranne quando si restringe la finestra
Private Function GetAvailableWidth() As Double
Dim presenter = FindVisualChild(Of DataGridCellsPresenter)(Me)
If presenter IsNot Nothing Then
Return presenter.ActualWidth
End If
Return ActualWidth ' fallback
End Function
'Private Function GetAvailableWidth() As Double
' Dim presenter = FindVisualChild(Of DataGridCellsPresenter)(Me)
' Dim sv = FindVisualChild(Of ScrollViewer)(Me)
' If Not IsNothing(presenter) Then
' If Not IsNothing(sv) Then
' If presenter.ActualWidth > sv.ViewportWidth Then
' Return sv.ViewportWidth
' Else
' Return presenter.ActualWidth
' End If
' Else
' Return presenter.ActualWidth
' End If
' End If
' Return ActualWidth ' fallback
'End Function
' calcola il valore sia quando si allarga che si restringe la finestra, ma gli mancano alcuni pixel in fondo a destra.
' non riuscendo a capire come calcolare questi pixel mancanti e' stata messa una costante di 6px che SEMBRA funzionare.
' non permette di allargare la colonna che si puo' ridimensionare!!!!!!!
'Private Const LayoutCorrection As Double = 6.0
'Private Function GetAvailableWidth() As Double
' Dim sv = FindVisualChild(Of ScrollViewer)(Me)
' If sv IsNot Nothing AndAlso sv.ViewportWidth > 0 Then
' Return sv.ViewportWidth - LayoutCorrection
' End If
' Return Me.ActualWidth - LayoutCorrection
'End Function
Private Function GetTotalColumnWidth() As Double
Return Columns.Where(Function(c) c.Visibility = Visibility.Visible).Sum(Function(c) c.ActualWidth)
End Function
' riduce ultima colonna
'Private Sub AdjustLastColumnFillMode()
' Dim availableWidth = GetAvailableWidth()
' Dim totalWidth = GetTotalColumnWidth()
' Dim lastVisible = Columns.Where(Function(c) c.Visibility = Visibility.Visible).LastOrDefault()
' If lastVisible Is Nothing OrElse Not _columnToLayout.ContainsKey(lastVisible) Then Exit Sub
' Dim layout = _columnToLayout(lastVisible)
' If layout.WidthUnitType = DataGridLengthUnitType.Auto Then Exit Sub
' If totalWidth < availableWidth Then
' ' Switch to Star to fill remaining space
' lastVisible.Width = New DataGridLength(1, DataGridLengthUnitType.Star)
' layout.WidthUnitType = DataGridLengthUnitType.Star
' layout.WidthValue = 1
' ElseIf layout.WidthUnitType = DataGridLengthUnitType.Star Then
' ' Revert to Pixel if total width exceeds available
' lastVisible.Width = New DataGridLength(lastVisible.ActualWidth, DataGridLengthUnitType.Pixel)
' layout.WidthUnitType = DataGridLengthUnitType.Pixel
' layout.WidthValue = lastVisible.ActualWidth
' End If
'End Sub
' riducce colonna con indice minore
'Private Sub AdjustLastColumnFillMode()
' Dim availableWidth = GetAvailableWidth()
' Dim totalWidth = GetTotalColumnWidth()
' ' Find the visible column with the smallest AdjustColumnIndex >= 0
' Dim targetPair = _columnToLayout _
' .Where(Function(kvp) kvp.Key.Visibility = Visibility.Visible AndAlso kvp.Value.AdjustColumnIndex >= 0) _
' .OrderBy(Function(kvp) kvp.Value.AdjustColumnIndex) _
' .FirstOrDefault()
' If targetPair.Key Is Nothing Then Exit Sub
' Dim column = targetPair.Key
' Dim layout = targetPair.Value
' ' Auto columns cannot be adjusted
' If layout.WidthUnitType = DataGridLengthUnitType.Auto Then Exit Sub
' If totalWidth < availableWidth Then
' ' Expand to fill remaining space
' column.Width = New DataGridLength(1, DataGridLengthUnitType.Star)
' layout.WidthUnitType = DataGridLengthUnitType.Star
' layout.WidthValue = 1
' ElseIf layout.WidthUnitType = DataGridLengthUnitType.Star Then
' ' Shrink back to pixel when space is insufficient
' column.Width = New DataGridLength(column.ActualWidth, DataGridLengthUnitType.Pixel)
' layout.WidthUnitType = DataGridLengthUnitType.Pixel
' layout.WidthValue = column.ActualWidth
' End If
'End Sub
Private Const WidthTolerance As Double = 2.0 ' evita micro oscillazioni
' riducce colonna con indice minore con tolleranza di movimento e tutto in pixel
Private Sub AdjustLastColumnFillMode()
Dim availableWidth = GetAvailableWidth()
Dim totalWidth = GetTotalColumnWidth()
Dim delta = availableWidth - totalWidth
Dim ViewportWidth = 0
Dim sv = FindVisualChild(Of ScrollViewer)(Me)
If Not IsNothing(sv) AndAlso Not Double.IsInfinity(sv.ViewportWidth) Then
ViewportWidth = sv.ViewportWidth
If availableWidth > ViewportWidth Then
If totalWidth < ViewportWidth Then
' correzione per restringimento colonna con doppio click su lato header
delta = ViewportWidth - totalWidth
Else
Debug.WriteLine(String.Format("available={0}, total={1}, delta={2}, viewport={3}", {availableWidth, totalWidth, delta, ViewportWidth}))
Return
End If
Else
Debug.WriteLine(String.Format("available={0}, total={1}, delta={2}", {availableWidth, totalWidth, delta}))
End If
End If
Debug.WriteLine(String.Format("available={0}, total={1}, delta={2}", {availableWidth, totalWidth, delta}))
' Se la differenza è minima, non fare nulla
If Math.Abs(delta) < WidthTolerance Then Exit Sub
' Trova la colonna regolabile (visibile, AdjustColumnIndex >= 0, indice minore)
Dim targetPair = _columnToLayout _
.Where(Function(kvp) kvp.Key.Visibility = Visibility.Visible AndAlso kvp.Value.AdjustColumnIndex >= 0) _
.OrderBy(Function(kvp) kvp.Value.AdjustColumnIndex) _
.FirstOrDefault()
If targetPair.Key Is Nothing Then Exit Sub
Dim column = targetPair.Key
Dim layout = targetPair.Value
' Usa sempre Pixel per evitare oscillazioni
Dim currentWidth As Double =
If(column.Width.IsAbsolute, column.Width.Value, column.ActualWidth)
' Nuova larghezza = larghezza attuale + spazio libero (positivo o negativo)
Dim newWidth As Double = currentWidth + delta
' Applica la nuova larghezza
column.Width = New DataGridLength(newWidth, DataGridLengthUnitType.Pixel)
layout.WidthUnitType = DataGridLengthUnitType.Pixel
layout.WidthValue = newWidth
End Sub
Private Sub RemoveColumnFromLayout(layout As ColumnLayout)
Dim column = _columnToLayout.FirstOrDefault(Function(c) c.Value Is layout).Key
If column IsNot Nothing Then
Columns.Remove(column)
_columnToLayout.Remove(column)
End If
ColumnLayouts.Remove(layout)
UpdateDisplayIndexes()
End Sub
Private Sub ApplyLayout(column As DataGridColumn, layout As ColumnLayout)
column.Width = New DataGridLength(layout.WidthValue, layout.WidthUnitType)
column.Visibility = If(layout.IsVisible, Visibility.Visible, Visibility.Collapsed)
column.SortDirection = layout.SortDirection
If LockAutoColumn AndAlso layout.WidthUnitType = DataGridLengthUnitType.Auto Then
column.CanUserResize = False
Else
column.CanUserResize = layout.CanUserResize
End If
column.CanUserReorder = layout.CanUserReorder
column.CanUserSort = layout.CanUserSort
column.IsReadOnly = layout.IsReadOnly
End Sub
Private Sub ApplyLockToAllColumns(ByVal locked As Boolean)
For Each kvp In _columnToLayout
Dim column = kvp.Key
Dim layout = kvp.Value
If locked Then
column.CanUserResize = False
column.CanUserReorder = False
column.CanUserSort = False
Else
' Quando sblocchi, risincronizzi con il layout
ApplyLayout(column, layout)
End If
Next
End Sub
Private Function CloneColumn(original As DataGridColumn) As DataGridColumn
Dim clone = DirectCast(Activator.CreateInstance(original.GetType()), DataGridColumn)
clone.Header = original.Header
clone.HeaderTemplate = original.HeaderTemplate
clone.SortMemberPath = original.SortMemberPath
clone.Visibility = original.Visibility
clone.Width = original.Width
If Not IsNothing(original.HeaderStyle) Then
clone.HeaderStyle = original.HeaderStyle
ElseIf Not IsNothing(Me.ColumnHeaderStyle) Then
clone.HeaderStyle = Me.ColumnHeaderStyle
End If
If Not IsNothing(original.CellStyle) Then
clone.CellStyle = original.CellStyle
ElseIf Not IsNothing(Me.CellStyle) Then
clone.CellStyle = Me.CellStyle
End If
If TypeOf original Is DataGridBoundColumn AndAlso TypeOf clone Is DataGridBoundColumn Then
Dim boundOriginal As DataGridBoundColumn = DirectCast(original, DataGridBoundColumn)
Dim boundClone As DataGridBoundColumn = DirectCast(clone, DataGridBoundColumn)
boundClone.Binding = boundOriginal.Binding
End If
If TypeOf original Is DataGridTemplateColumn AndAlso TypeOf clone Is DataGridTemplateColumn Then
Dim templateOriginal As DataGridTemplateColumn = DirectCast(original, DataGridTemplateColumn)
Dim templateClone As DataGridTemplateColumn = DirectCast(clone, DataGridTemplateColumn)
templateClone.CellTemplate = templateOriginal.CellTemplate
templateClone.CellEditingTemplate = templateOriginal.CellEditingTemplate
End If
If TypeOf original Is DataGridComboBoxColumn Then
Dim comboOriginal As DataGridComboBoxColumn = DirectCast(original, DataGridComboBoxColumn)
' Creiamo una TemplateColumn che rimpiazza la ComboBoxColumn
Dim templateCol As New DataGridTemplateColumn()
templateCol.Header = comboOriginal.Header
templateCol.SortMemberPath = comboOriginal.SortMemberPath
templateCol.Visibility = comboOriginal.Visibility
' --- TEMPLATE PER LA CELLA (visualizzazione) ---
Dim cellFactory As New FrameworkElementFactory(GetType(TextBlock))
If comboOriginal.SelectedValueBinding IsNot Nothing Then
cellFactory.SetBinding(TextBlock.TextProperty, comboOriginal.SelectedValueBinding)
ElseIf comboOriginal.SelectedItemBinding IsNot Nothing Then
cellFactory.SetBinding(TextBlock.TextProperty, comboOriginal.SelectedItemBinding)
Else
' fallback: mostra il primo elemento
cellFactory.SetBinding(TextBlock.TextProperty, New Binding("."))
End If
templateCol.CellTemplate = New DataTemplate() With {.VisualTree = cellFactory}
' --- TEMPLATE PER LA CELLA IN EDITING ---
Dim editFactory As New FrameworkElementFactory(GetType(ComboBox))
' Copia ItemsSource (binding o valore)
Dim itemsBinding = BindingOperations.GetBinding(comboOriginal, DataGridComboBoxColumn.ItemsSourceProperty)
If itemsBinding IsNot Nothing Then
editFactory.SetBinding(ComboBox.ItemsSourceProperty, itemsBinding)
Else
editFactory.SetValue(ComboBox.ItemsSourceProperty, comboOriginal.ItemsSource)
End If
' Copia SelectedItem / SelectedValue
If comboOriginal.SelectedItemBinding IsNot Nothing Then
editFactory.SetBinding(ComboBox.SelectedItemProperty, comboOriginal.SelectedItemBinding)
End If
If comboOriginal.SelectedValueBinding IsNot Nothing Then
editFactory.SetBinding(ComboBox.SelectedValueProperty, comboOriginal.SelectedValueBinding)
End If
' Copia DisplayMemberPath / SelectedValuePath
If Not String.IsNullOrEmpty(comboOriginal.DisplayMemberPath) Then
editFactory.SetValue(ComboBox.DisplayMemberPathProperty, comboOriginal.DisplayMemberPath)
End If
If Not String.IsNullOrEmpty(comboOriginal.SelectedValuePath) Then
editFactory.SetValue(ComboBox.SelectedValuePathProperty, comboOriginal.SelectedValuePath)
End If
templateCol.CellEditingTemplate = New DataTemplate() With {.VisualTree = editFactory}
Return templateCol
End If
Return clone
End Function
Private Function FindHeaderForColumn(ByVal column As DataGridColumn) As DataGridColumnHeader
Dim presenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(Me)
If presenter Is Nothing Then Return Nothing
For i As Integer = 0 To Columns.Count - 1
Dim header = TryCast(presenter.ItemContainerGenerator.ContainerFromIndex(i), DataGridColumnHeader)
If header?.Column Is column Then Return header
Next
Return Nothing
End Function
Private Function FindVisualChild(Of T As DependencyObject)(ByVal parent As DependencyObject) As T
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(parent) - 1
Dim child = VisualTreeHelper.GetChild(parent, i)
If TypeOf child Is T Then Return DirectCast(child, T)
Dim result = FindVisualChild(Of T)(child)
If result IsNot Nothing Then Return result
Next
Return Nothing
End Function
Private Function CreateHeaderContextMenu(ByVal layout As ColumnLayout) As ContextMenu
Dim menu = New ContextMenu()
If HeaderContextMenuStyle IsNot Nothing Then
menu.Style = HeaderContextMenuStyle
End If
If IsTableLocked Then
menu.Items.Add(CreateToggleMenuItem("Lock Table", IsTableLocked, Sub(val) IsTableLocked = val))
Else
menu.Items.Add(CreateToggleMenuItem("Can Sort", layout.CanUserSort, Sub(val) layout.CanUserSort = val))
If layout.CanUserReorderUserEditable Then
menu.Items.Add(CreateToggleMenuItem("Can Reorder", layout.CanUserReorder, Sub(val) layout.CanUserReorder = val))
End If
If layout.WidthUnitType <> DataGridLengthUnitType.Auto Then
menu.Items.Add(CreateToggleMenuItem("Can Resize", layout.CanUserResize, Sub(val) layout.CanUserResize = val))
End If
If layout.IsVisibilityUserEditable Then
menu.Items.Add(CreateToggleMenuItem("Visible", layout.IsVisible, Sub(val)
layout.IsVisible = val
Dispatcher.BeginInvoke(
New Action(Sub() AdjustLastColumnFillMode()),
DispatcherPriority.Background
)
End Sub))
End If
menu.Items.Add(New Separator())
menu.Items.Add(CreateActionMenuItem("Reset Sort", Sub() layout.SortDirection = Nothing))
menu.Items.Add(CreateToggleMenuItem("Lock Table", IsTableLocked, Sub(val) IsTableLocked = val))
Dim hiddenMenu = New MenuItem With {.Header = "Colonne nascoste",
.Style = HeaderContextMenuItemStyle}
hiddenMenu.Items.Add(New MenuItem With {.Header = "(nessuna)", .IsEnabled = False})
AddHandler hiddenMenu.SubmenuOpened, Sub()
hiddenMenu.Items.Clear()
Dim hiddenLayouts = ColumnLayouts.Where(Function(l) Not l.IsVisible AndAlso l.IsVisibilityUserEditable).ToList()
If hiddenLayouts.Count = 0 Then
hiddenMenu.Items.Add(New MenuItem With {.Header = "(nessuna)",
.IsEnabled = False,
.Style = HeaderContextMenuItemStyle})
Else
For Each hiddenLayout In hiddenLayouts
Dim Column = _columnToLayout.FirstOrDefault(Function(c) c.Value Is hiddenLayout).Key
Dim item = New MenuItem With {.Header = Column.Header,
.Style = HeaderContextMenuItemStyle}
AddHandler item.Click, Sub()
hiddenLayout.IsVisible = True
End Sub
hiddenMenu.Items.Add(item)
Next
End If
End Sub
menu.Items.Add(hiddenMenu)
End If
Return menu
End Function
Private Sub RefreshAllHeaderMenus()
For Each column In Columns
Dim header = FindHeaderForColumn(column)
If header IsNot Nothing AndAlso _columnToLayout.ContainsKey(column) Then
header.ContextMenu = CreateHeaderContextMenu(_columnToLayout(column))
End If
Next
End Sub
Private Function CreateToggleMenuItem(ByVal label As String, ByVal currentValue As Boolean, ByVal setter As Action(Of Boolean)) As MenuItem
Dim item = New MenuItem With {
.Header = label,
.IsCheckable = True,
.IsChecked = currentValue
}
AddHandler item.Checked, Sub() setter(True)
AddHandler item.Unchecked, Sub() setter(False)
If HeaderContextMenuItemStyle IsNot Nothing Then
item.Style = HeaderContextMenuItemStyle
End If
Return item
End Function
Private Function CreateActionMenuItem(ByVal label As String, ByVal action As Action) As MenuItem
Dim item = New MenuItem With {.Header = label}
AddHandler item.Click, Sub() action()
If HeaderContextMenuItemStyle IsNot Nothing Then
item.Style = HeaderContextMenuItemStyle
End If
Return item
End Function
Private Sub UpdateDisplayIndexes()
Dim nSkippedColumn As Integer = 0
For i As Integer = 0 To ColumnLayouts.Count - 1
Dim layout = ColumnLayouts(i)
Dim column = _columnToLayout.FirstOrDefault(Function(p) p.Value Is layout).Key
If Not IsNothing(column) Then
column.DisplayIndex = i - nSkippedColumn
Else
nSkippedColumn += 1
End If
Next
End Sub
Public Shared Function WriteColumnLayout(path As String, Name As String, columns As ObservableCollection(Of ColumnLayout)) As Boolean
Dim GridList As Dictionary(Of String, List(Of ColumnLayout))
If File.Exists(path) Then
Dim JsonOriginalContent = File.ReadAllText(path)
GridList = JsonConvert.DeserializeObject(Of Dictionary(Of String, List(Of ColumnLayout)))(JsonOriginalContent)
If GridList.ContainsKey(Name) Then
GridList(Name) = columns.ToList()
Else
GridList.Add(Name, columns.ToList())
End If
Else
GridList = New Dictionary(Of String, List(Of ColumnLayout))
GridList.Add(Name, columns.ToList())
End If
Dim JsonNewContent = JsonConvert.SerializeObject(GridList, Formatting.Indented)
File.WriteAllText(path, JsonNewContent)
Return True
End Function
Public Shared Function WriteColumnLayout(path As String, NewGridList As Dictionary(Of String, ObservableCollection(Of ColumnLayout))) As Boolean
Dim OrigGridList As Dictionary(Of String, List(Of ColumnLayout))
If File.Exists(path) Then
Dim JsonOriginalContent = File.ReadAllText(path)
OrigGridList = JsonConvert.DeserializeObject(Of Dictionary(Of String, List(Of ColumnLayout)))(JsonOriginalContent)
For Each Grid In NewGridList
If OrigGridList.ContainsKey(Grid.Key) Then
OrigGridList(Grid.Key) = Grid.Value.ToList()
Else
OrigGridList.Add(Grid.Key, Grid.Value.ToList())
End If
Next
Else
OrigGridList = New Dictionary(Of String, List(Of ColumnLayout))
For Each Grid In NewGridList
OrigGridList.Add(Grid.Key, Grid.Value.ToList())
Next
End If
Dim JsonNewContent = JsonConvert.SerializeObject(OrigGridList, Formatting.Indented)
File.WriteAllText(path, JsonNewContent)
Return True
End Function
Public Shared Function ReadColumnLayout(path As String, Name As String, ByRef ColumnList As ObservableCollection(Of ColumnLayout)) As Boolean
If Not File.Exists(path) Then Return False
ColumnList.Clear()
Dim JsonOriginalContent = File.ReadAllText(path)
Dim GridList As Dictionary(Of String, List(Of ColumnLayout)) = JsonConvert.DeserializeObject(Of Dictionary(Of String, List(Of ColumnLayout)))(JsonOriginalContent)
If GridList.ContainsKey(Name) Then
ColumnList = New ObservableCollection(Of ColumnLayout)(GridList(Name))
Return True
Else
Return False
End If
End Function
End Class