ab3fe0ee70
- aggiunta verifica che dimensione ViewPort non sia Infinity in EgwDataGrid - eliminata colonna frozen a sinistra di default in EgwRightFrozenDataGrid
849 lines
38 KiB
VB.net
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 |