VERSION 5.00
Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} CSE_ListSelector 
   Caption         =   "Выбор списка значений"
   ClientHeight    =   8910.001
   ClientLeft      =   45
   ClientTop       =   375
   ClientWidth     =   8400.001
   OleObjectBlob   =   "CSE_ListSelector.frx":0000
   StartUpPosition =   1  'CenterOwner
End
Attribute VB_Name = "CSE_ListSelector"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' =========== List selection dialog ================
' Shared module version: 20220703
' Tested in:
' Depends on: ex_MSHook
' Required reference: Scripting
Option Explicit
Option Compare Text

Private input_ As Excel.ListObject
Private items_() As String
Private selected_ As Scripting.Dictionary
Private isUpdating_ As Boolean

Public delimiter_ As String
Public isCanceled_ As Boolean

Private Sub UserForm_Initialize()
  isUpdating_ = False
  isCanceled_ = True
  Call PositionInMiddle
End Sub

Private Sub UserForm_Finalize()
  Call HookDisable
End Sub

Private Sub UserForm_Activate()
  Call HookEnable(Me, "ProcessMouseWheelMovement")
End Sub

Private Sub UserForm_Deactivate()
  Call HookDisable
End Sub

Public Function Init(iData As Excel.ListObject, Optional bAllowNewItems As Boolean = True)
  Set input_ = iData
  delimiter_ = TBDelim.Text
  
  Call ResetSelection
  Call ScanInput
  Call OnFilter("")
  
  TBNewItem.Enabled = bAllowNewItems
  BtnAddItem.Enabled = bAllowNewItems
End Function

Public Function GetSelectedStr() As String
  Dim sText$: sText = vbNullString
  Dim vItem As Variant
  For Each vItem In selected_.Keys
    If sText <> vbNullString Then _
      sText = sText & delimiter_
    sText = sText & CStr(vItem)
  Next vItem
  GetSelectedStr = sText
End Function

' ========== Event handlers ==========
Private Sub OkBtn_Click()
  isCanceled_ = False
  Call Me.Hide
  Call HookDisable
End Sub

Private Sub CancelBtn_Click()
  isCanceled_ = True
  Call Me.Hide
  Call HookDisable
End Sub

Private Sub ClearListBtn_Click()
  Call ResetSelection
  Call OnFilter(TBFilter.Text)
End Sub

Private Sub TBFilter_Change()
  Call OnFilter(TBFilter.Text)
End Sub

Private Sub TBDelim_Change()
  delimiter_ = TBDelim.Text
End Sub

Private Sub LBItems_Change()
  If isUpdating_ Then _
    Exit Sub
    
  Dim nItem&
  For nItem = 0 To LBItems.ListCount - 1 Step 1
    Dim sText$: sText = LBItems.List(nItem)
    If LBItems.Selected(nItem) Then
      If Not selected_.Exists(sText) Then _
        Call selected_.Add(sText, 0)
    Else
      If selected_.Exists(sText) Then _
        Call selected_.Remove(sText)
    End If
  Next nItem
End Sub

Private Sub BtnAddItem_Click()
  Dim sNewItem$: sNewItem = TBNewItem.Text
  If sNewItem = vbNullString Then _
    Exit Sub
  Dim nItem&
  For nItem = LBound(items_) To UBound(items_) Step 1
    If items_(nItem) = sNewItem Then
      Call UserInteraction.ShowMessage(EM_ITEM_EXISTS, sNewItem)
      Exit Sub
    End If
  Next nItem
  
  Dim iRow As Excel.ListRow: Set iRow = input_.ListRows.Add(1)
  iRow.Range.Cells(1, 1) = sNewItem
  Call ScanInput
  Call OnFilter(TBFilter.Text)
End Sub

' ======= Mouse scrolling for listbox ========
Public Function ProcessMouseWheelMovement(nDirection&)
  If nDirection > 0 Then
    If LBItems.TopIndex > 0 Then _
      LBItems.TopIndex = LBItems.TopIndex - 1
  Else
    If LBItems.TopIndex < LBItems.ListCount - 1 Then _
      LBItems.TopIndex = LBItems.TopIndex + 1
  End If
End Function

' =========
Private Function PositionInMiddle()
  Dim nTopOffset&: nTopOffset = (Application.UsableHeight / 2) - (Me.Height / 2)
  Dim nLeftOffset&: nLeftOffset = (Application.UsableWidth / 2) - (Me.Width / 2)
  
  Me.StartUpPosition = 0
  Me.Top = Application.Top + IIf(nTopOffset > 0, nTopOffset, 0)
  Me.Left = Application.Left + IIf(nLeftOffset > 0, nLeftOffset, 0)
End Function

Private Function ScanInput()
  ReDim items_(1 To input_.Range.Rows.Count - 1)
  Dim nRow&
  For nRow = 2 To input_.Range.Rows.Count Step 1
    items_(nRow - 1) = input_.Range.Cells(nRow, 1)
  Next nRow
End Function

Private Function ResetSelection()
  Set selected_ = New Scripting.Dictionary
  selected_.CompareMode = TextCompare
End Function

Private Function OnFilter(sFilter$)
  Call LBItems.Clear
  Dim nItem&
  For nItem = LBound(items_) To UBound(items_) Step 1
    If sFilter = vbNullString Or VBA.InStr(1, items_(nItem), sFilter, vbTextCompare) <> 0 Then
      Call LBItems.AddItem(items_(nItem))
    End If
  Next nItem
  Call UpdateSelected
  Call UpdateCount
End Function

Private Function UpdateCount()
  TBViewCount.Value = LBItems.ListCount
  TBTotalCount.Value = ArraySize(items_)
  TBSelectedCount.Value = selected_.Count
End Function

Private Function UpdateSelected()
  isUpdating_ = True
  
  If LBItems.ListCount = 0 Then _
    Exit Function
  
  Dim nItem&
  For nItem = 0 To LBItems.ListCount - 1 Step 1
    LBItems.Selected(nItem) = selected_.Exists(LBItems.List(nItem))
  Next nItem
  
  isUpdating_ = False
End Function
