Pole se seznamem (anglicky ComboBox), je kombinovaný ovládací prvek sestávající z textového pole (samostatně jako TextBox) a seznamu (ListBox). Někdy mu také podomácku říkám „roletka“. Umožňuje zadání vstupu ručně či výběr ze seznamu přednastavených položek.
Abychom mohli daný prvek nasadit na listu, musíme si nejprve zobrazit na Pásu karet kartu Vývojář.
Ve skupině Ovládací prvky máme možnost vložit dva typy těchto prvků – formulářový (historicky starší, jednodušší) a prvek ActiveX (podobný, ale ne identický s tím, jaký znáte z formulářů VBA).
Pole se seznamem (ovládací prvek formuláře)
Práci s ním jsem se pokusil shrnout v obrázku. Pozn. Slovo „formuláře“ nemá nic společného s novodobými formuláři UserForm, se kterými pracujeme níže.
Výběr probíhá přes tlačítko Vložit a samotné vykreslení pak s pomocí myšky. Pokud chcete prvek zarovnat do mřížky, přidržte klávesu ALT. Pro editaci a přesun jej vždy vybírejte pravým tlačítkem myši. Vlastnosti najdete v kontextovém menu pod volbou Formát ovládacího prvku. Otevřený dialog nese stejný název. Možnosti jsou vcelku strohé a zřejmé. Okno po úpravám zavřeme a před použitím prvku už stačí jen zbavit jej zaměření (klepneme myškou jinam).
Provázaná buňka kdo ví proč vrací pořadí vybrané položky a k jejímu obsahu nám dopomůže až vyhledávací funkce (např. INDEX). Spojení funguje obousměrně, změna čísla v buňce má za následek výběr odpovídající položky v prvku (nula a menší číslo prvek vyčistí).
U prvku formuláře nezměníte barevnost ani písmo, neumí zobrazit více sloupců a v seznamu není možné vyhledávat ani se přesouvat mezi položkami kolečkem myši. Prvku lze přiřadit makro (viz také pravé tlačítko myši). Ve VBA spadá tento prvek do kolekce Shapes (název se na listu ukazuje v Poli názvů).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Sub PrvekFormularePristup() Dim shpPrvek As Shape Dim strPropojenaBunka As String Dim strZdrojDat As String Set shpPrvek = ActiveSheet.Shapes("stahovací 1") 'vlastnosti pod ControlFormat nebo DrawingObject strPropojenaBunka = shpPrvek.ControlFormat.LinkedCell strZdrojDat = shpPrvek.ControlFormat.ListFillRange End Sub |
Pole se seznamem (ovládací prvek ActiveX)
I zde by měl napovědět hodně obrázek.
Výběr a vykreslení se děje stejně jako v předchozím případě. Excel se přitom přepíná do Režimu návrhu a v něm také probíhají veškeré úpravy. Stačí klepnout v Pásu karet na Vlastnosti a pohrát si v otevřeném dialogu s názvem Properties (znáte možná už z editoru VBA). Nejpodstatnější jsou pro tuto chvíli vlastnosti ListFillRange (zdroj dat) a LinkedCell (propojená buňka), na zbytek se podíváme později. Na rozdíl od prvku formuláře vrací tento prvek primárně obsah vybrané položky.
Makro je zpravidla vázáné na událost Change tohoto prvku. Stačí na ComboBox poklepat v Režimu návrhu a Excel vás přepne do editoru VBA. Prvek je umístěn v modulu daného listu. Procedura níže je ukázkou toho, jak makrem vracet pořadí vybrané položky.
1 2 3 4 5 6 7 8 9 10 11 12 | Private Sub ComboBox1_Change() With ComboBox1 'zápis pořadí položky do buňky 'nebyla vybrána položka .. ListIndex = -1 'první položka ... ListIndex = 0 Range("C20").Value = .ListIndex + 1 End With End Sub |
Pokud máme hotovo, vypneme Režim návrhu a je to.
Pozn. Ani jeden z prvků (formuláře, ActiveX) neumí vybírat více položek naráz, k tomu účelu slouží ListBox (o něm zase někdy jindy). V rámci položek se nemohou objevit obrázky (nastudujte prvek Microsoft ImageComboBox). Grafický vzhled ComboBoxu pod VBA neřídí XP styly (nedoporučuji měnit manifest pro Excel.exe!).
Jak naplnit ComboBox daty
Nadále budeme hovořit už jen o prvku ActiveX na formuláři UserForm. Zdrojem dat pro seznamy nemusí být nutně oblast buněk na listu. Položky lze přidávat jednotlivě (v cyklu) přes metodu AddItem nebo hromadně přiřazením pole do vlastnosti List. Nejprve základy. Přidat ComboBox na formulář (UserForm) předpokládám nepředstavuje problém.
Okno Properties najdete v panelu nástrojů pod View / Properties Windows (klávesa F4).
Hurá na příklady. Formuláře si můžete vyzkoušet v návaznosti na List1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | Private Sub UserForm_Initialize() Dim i As Integer Dim strZnaky As String 'ComboBox1 '********* With ComboBox1 'jednotlivé přidání položek .AddItem "raz" .AddItem "dva" .AddItem "tři" End With 'ComboBox2 '********* With ComboBox2 'jednotlivé přidání položek .AddItem "10,25" .AddItem "10,50" .AddItem "10,75" End With 'ComboBox3 '********* 'přiřazení položek z oblasti buněk 'uvedení Value nutné ComboBox3.List = Range("B2:B4").Value 'ComboBox4 '********* 'přiřazení položek z pojmenované oblasti buněk 'uvedení Value nutné 'ColumnCount = 2 ... dvousloupcový seznam 'ColumnHeads = True ... hlavička (vždy z oblasti listu) ComboBox4.List = Range("rngTabulka").Value 'ComboBox5 '********* Dim arrPoleArray 'pole položek arrPoleArray = Array("jablko", "hruška", "švestka") 'přiřazení pole do prvku ComboBox5.List = arrPoleArray 'ComboBox6 '********* Dim arrStatickePole(1 To 3) arrStatickePole(1) = "ano" arrStatickePole(2) = "ne" arrStatickePole(3) = "nevím" 'přiřazení pole do prvku ComboBox6.List = arrStatickePole 'ComboBox7 '********* 'řetezec položek strOddelenePolozky = "Spejbl,Hurvínek,Mánička" 'převedení do pole položek arrOddelenePolozky = Split(strOddelenePolozky, ",") 'přiřazení pole do prvku ComboBox7.List = arrOddelenePolozky 'ComboBox8 '********* For i = 1 To 5 'přidání položky do prvku ComboBox8.AddItem "Pokoj " & i Next i 'ComboBox9 '********* strZnaky = "abcdefghijklmnopqrstuvwxyz" For i = 1 To Len(strZnaky) 'přidání položky do prvku ComboBox9.AddItem Mid$(strZnaky, i, 1) Next i End Sub Private Sub ComboBox2_Change() 'přenos číselné hodnoty do listu Range("B9") = CDbl(ComboBox2.Value) End Sub |
Následuje ukázka, jak naplnit prvek položkami času.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | Private Sub UserForm_Initialize() Dim dtmCasOd As Date Dim dtmCasDo As Date Dim dtmCasKrok As Date Dim intKrokHodiny As Integer Dim intKrokMinuty As Integer Dim intKrokSekundy As Integer Dim sngCasKrok As Single Dim sngCasRozdil As Single Dim lngPocetJednotek As Long 'dílčí jednotky kroku intKrokHodiny = 0 intKrokMinuty = 30 intKrokSekundy = 0 'čas dtmCasKrok = TimeSerial(intKrokHodiny, intKrokMinuty, intKrokSekundy) sngCasKrok = 24 * dtmCasKrok 'počet jednotek za den lngPocetJednotek = 24 / sngCasKrok 'ComboBox1 '********* 'čas For i = 1 To lngPocetJednotek 'přidání položky do prvku ComboBox1.AddItem Format((i - 1) * dtmCasKrok, "Hh:Nn") Next i 'ComboBox2 '********* 'doba trvání (přes 24 hodin) For i = 1 To lngPocetJednotek 'přidání položky do prvku ComboBox2.AddItem WorksheetFunction.Text(i * dtmCasKrok, "[hh]:mm") Next i 'ComboBox3 '********* 'časový interval (např. směna) od-do dtmCasOd = TimeSerial(6, 0, 0) dtmCasDo = TimeSerial(14, 0, 0) 'dílčí jednotky kroku intKrokHodiny = 0 intKrokMinuty = 15 intKrokSekundy = 0 'čas dtmCasKrok = TimeSerial(intKrokHodiny, intKrokMinuty, intKrokSekundy) sngCasKrok = 24 * dtmCasKrok sngCasRozdil = 24 * (dtmCasDo - dtmCasOd) 'počet jednotek v intervalu lngPocetJednotek = sngCasRozdil / sngCasKrok 'čas For i = 1 To (lngPocetJednotek + 1) 'přidání položky do prvku ComboBox3.AddItem Format(dtmCasOd + (i - 1) * dtmCasKrok, "Hh:Nn") Next i End Sub Private Sub ComboBox1_Change() 'vložení hodnoty do buňky Range("B13").Value = ComboBox1.Value End Sub Private Sub ComboBox2_Change() 'vložení hodnoty do buňky Range("B13").Value = ComboBox2.Value End Sub Private Sub ComboBox3_Change() 'vložení hodnoty do buňky Range("B13").Value = ComboBox3.Value End Sub |
Když jsem uvedl čas, nemůže chybět ani datum. Tentokrát jsem zvolil praktickou ukázku s výběrem roku (ComboBox 1), měsíce (ConboBox2) a tomu odpovídajícímu počtu datumů (ComboBox3). Bonusově jsem poté přihodil k pondělním datumům ještě ISO číslo týdne roku a další vychytávky.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | Private Sub UserForm_Initialize() Dim intRok As Integer intRok = Year(Now) With cboRok 'naplnění prvku roky 'tento rok a minulý rok .AddItem intRok - 1 .AddItem intRok 'předvýběr druhé položky .ListIndex = 1 End With With cboMesic 'naplnění prvku číselnými hodnotami 'lze řešit v cyklu 'zde s využitím metody Evaluate a vzorce listu .List = [ROW(1:12)] 'předvýběr položky aktuálního měsíce .ListIndex = Month(Now) - 1 End With With cboMesicDatumy 'předvýběr položky aktuálního dne .ListIndex = Day(Now) - 1 End With End Sub Private Sub cboMesic_Change() 'volání procedury Zmena Call Zmena End Sub Private Sub cboRok_Change() 'volání procedury Zmena Call Zmena End Sub Private Sub CommandButton1_Click() 'vložení hodnoty do buňky Range("B11") = CDate(Left(cboMesicDatumy.Text, 10)) End Sub Sub Zmena() Dim i As Integer Dim intRok As Integer Dim intMesic As Integer Dim intMesicPocetDni As Integer Dim intTydenRoku As Integer Dim dtmDate As Date 'v případě chyby (neúplná data při inicializaci formuláře) 'ukončit proceduru On Error GoTo Konec 'rok intRok = cboRok.Value 'měsíc intMesic = cboMesic.Value 'počet dní (trik) intMesicPocetDni = Day(DateSerial(intRok, intMesic + 1, 0)) With cboMesicDatumy 'vymazání stávajících položek .Clear For i = 1 To intMesicPocetDni dtmDate = DateSerial(cboRok.Value, intMesic, i) If Weekday(dtmDate, vbMonday) > 1 Then 'přidání datumu do prvku .AddItem Format(dtmDate, "dd.mm.yyyy") Else 'týden roku (ISO) intTydenRoku = DatePart("ww", dtmDate, vbMonday, _ vbFirstFourDays) 'přidání datumu a týdne roku do prvku .AddItem Format(dtmDate, "dd.mm.yyyy") & " (" & intTydenRoku & _ ")" End If Next i 'předvýběr první položky .ListIndex = 0 End With Konec: End Sub |
Pokračujeme závislými seznamy otevřených sešitů a listů v nich.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | Private Sub UserForm_Initialize() Dim wkbSesit As Workbook 'ComboBox1 '********* 'pro každý otevřený sešit For Each wkbSesit In Application.Workbooks 'přidání názvu sešitu do prvku cboSesity.AddItem wkbSesit.Name Next wkbSesit 'předvýběr aktivního sešitu cboSesity.Value = ActiveWorkbook.Name End Sub Private Sub cboSesity_Change() Dim shtList As Object Dim strPolozka As String 'vybraná položka (sešit) strPolozka = cboSesity.Value 'pro prvek s listy With cboListy 'vymazání stávajících položek .Clear 'pro každý list v aktivním sešitu For Each shtList In Workbooks(strPolozka).Sheets 'přidání názvu listu do prvku .AddItem shtList.Name Next shtList 'je vybrán aktivní sešit? If strPolozka = ActiveWorkbook.Name Then 'předvýběr aktivního listu .Value = ActiveSheet.Name Else 'předvýběr první položky .ListIndex = 0 End If End With End Sub |
Když jsme načali sešity, zůstaňme ještě chvíli u výpisu souborů ze složky vybrané uživatelem. Nebude chybět filtrování přípony, ale vynecháme rekurzivní procházení podsložek.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | Private Sub Image1_Click() Dim strSlozka As String Dim arrPoleSouboru 'víceúčelový dialog, zde pro výběr složky With Application.FileDialog(msoFileDialogFolderPicker) 'titulek, běžně "Procházet" .Title = "Výběr složky" 'výchozí styl zobrazení, zde náhledy obsahu složek 'Windows 7, 64 bit, nefunkční '.InitialView = msoFileDialogViewLargeIcons 'vícenásobný výběr složek nelze použít '.AllowMultiSelect = True 'výchozí zobrazená složka, zde složka tohoto souboru .InitialFileName = ThisWorkbook.Path 'popis tlačítka, běžně "OK" .ButtonName = "Vybrat" 'zobrazení dialogu .Show 'byla vybrána složka? If .SelectedItems.Count > 0 Then 'převzetí názvu složky strSlozka = .SelectedItems(1) End If End With 'načtení názvů souborů s odpovídající příponou (maska pro sešity) 'ze složky do pole a jejich abecední setřídění arrPoleSouboru = epfSlozkaSoubory(strSlozka & "", "*.xl*") 'bylo vráceno pole? If IsArray(arrPoleSouboru) Then 'vyčištení ovládacího prvku cboSoubory.Clear 'přiřazení pole do prvku cboSoubory.List = arrPoleSouboru Else 'zpráva MsgBox "Nebyly nalezeny žádné soubory.", vbInformation + vbOKOnly End If End Sub Function epfSlozkaSoubory(Slozka As String, Filtr As String) Dim arrPole() Dim strSouborNazev As String Dim iPocet As Integer strSouborNazev = Dir(Slozka & Filtr) Do While strSouborNazev <> "" If strSouborNazev <> "" Then iPocet = iPocet + 1 ReDim Preserve arrPole(1 To iPocet) arrPole(iPocet) = LCase(strSouborNazev) End If strSouborNazev = Dir() Loop If Not Len(Join(arrPole, "")) = 0 Then QuickSort arrPole, LBound(arrPole), UBound(arrPole) epfSlozkaSoubory = arrPole Else epfSlozkaSoubory = "" End If End Function Sub QuickSort(vArray As Variant, inLow As Long, inHi As Long) Dim pivot As Variant Dim tmpSwap As Variant Dim tmpLow As Long Dim tmpHi As Long tmpLow = inLow tmpHi = inHi pivot = vArray((inLow + inHi) \ 2) While (tmpLow < = tmpHi) While (vArray(tmpLow) < pivot And tmpLow < inHi) tmpLow = tmpLow + 1 Wend While (pivot < vArray(tmpHi) And tmpHi > inLow) tmpHi = tmpHi - 1 Wend If (tmpLow < = tmpHi) Then tmpSwap = vArray(tmpLow) vArray(tmpLow) = vArray(tmpHi) vArray(tmpHi) = tmpSwap tmpLow = tmpLow + 1 tmpHi = tmpHi - 1 End If Wend If (inLow < tmpHi) Then QuickSort vArray, inLow, tmpHi If (tmpLow < inHi) Then QuickSort vArray, tmpLow, inHi End Sub |
Jako zdroj pro ComboBox samozřejmě slouží i databáze. Zde krátká ukázka propojení z Microsoft Access.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | Private Sub UserForm_Initialize() Dim cn As Object Dim rs As Object Dim strSql As String Dim strConnection As String 'připojení do databáze (Microsoft Access) 'vytvoření objektu připojení Set cn = CreateObject("ADODB.Connection") 'řetězec pro připojení strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & "Data Source=" & _ ThisWorkbook.Path & "\databaze.mdb" 'SQL dotaz (obec začínající na Vče... a jeho PSČ) strSql = "SELECT DISTINCT Obec, PSC FROM PSC WHERE Obec Like 'Vče%'" 'připojení cn.Open strConnection 'recordset Set rs = cn.Execute(strSql) 'konverze (transpozice) záznamů do pole arrPoleObce = WorksheetFunction.Transpose(rs.GetRows) 'přiřazení pole do prvku ComboBox1.List = arrPoleObce 'uzavření spojení rs.Close cn.Close 'uvolnění paměti Set rs = Nothing Set cn = Nothing End Sub |
V případě databází se využívají prakticky všechny způsoby načítání položek, od AddItem v cyklu po přiřazení pole (transformujeme výstupy GetRows, případně GetString, zbavujeme záznamy položek Null, upravujeme datový typ apod.). Pokud jste nedočetli před zkoušením do konce, pak vězte, že vícesloupcové ComboBoxy doprovází vlastnost ColumnCount (zde ColumnCount = 2).
Zbývají ještě drobné tipy na vytváření seznamů bez nutnosti textových polí (názvy dnů, měsíců).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Private Sub UserForm_Initialize() 'ComboBox1 '********* 'dny týdne slovně For i = 1 To 7 'přidání položky do prvku '24.4.2016 ... neděle ComboBox1.AddItem Format(DateSerial(2016, 4, 24) + i, "dddd") Next i 'ComboBox2 '********* 'měsíce slovně For i = 1 To 12 'přidání položky do prvku ComboBox2.AddItem Format(DateSerial(2016, i, 1) + i, "mmmm") Next i 'ComboBox3 '********* 'měsíce slovně (německy) For i = 1 To 12 'přidání položky do prvku ComboBox3.AddItem WorksheetFunction.Text(DateSerial(2016, i, 1) + i, _ "[$-407]mmmm") Next i End Sub |
Do sytosti jsme si pohráli a ještě zbývá vážnější zastavení se u vybraných vlastností ComboBoxu.
Vybrané vlastnosti (vícesloupcového) ComboBoxu
BoundColumn … číslo sloupce, ze kterého se bude brát hodnota Value (1 .. první sloupec, i skrytý)
ColumnsCount … určuje, kolik sloupců dat bude ComboBox obsahovat (včetně skrytých), pro skrytý sloupec je ColumnWidths = 0)
ColumnWidths … šířky sloupců v bodech (palcích, centimetrech) oddělených středníky (např. 0 pt;111 pt;109 pt), pro skrytý sloupec se zapisuje 0, šířku posledního sloupce lze vynechat (součet šířek nesedí přesně s vlastností Width prvku, teoreticky Width = součet šířek + ColumnsCount – 1)
LinkedCell … adresa propojené buňky (např. List1!D20), platí pro prvek ComboBox na listu
ListRows … počet viditelných položek po rozbalení seznamu bez nutnosti užití posuníku
ListWidth … šířka seznamu po rozbalení
MatchEntry … ponechávejte na 1-fmMatchEntryComplete, vyhledávání existující položky bude tak probíhat v souladu s posloupností zadávaných znaků v textovém poli, resp. při aktivaci prvku.
RowSource … adresa zdroje dat braného z listu (např. List1!G3:I6), platí pro prvek na formuláři UserForm, pro prvek ComboBox na listu jde o vlastnost ListFillRange
Style … pokud uživatel nemá mít možnost zapsat do textového pole prvku vlastní hodnotu a bude pouze vybírat z existujících položek, zvolte 2-fmSTyleDropDownList
TextColumn … ‚číslo sloupce, jehož položky se objevují po výběru ze seznamu v textovém poli a ve vlastnosti Text,
TextColumn = -1 … zobrazí hodnotu 1. sloupce s hodnotou ColumnWidths > 0, tj. prvního viditelného
TextColumn = 0 … zobrazí hodnotu vlastnosti ListIndex
TextColumn = 1 … zobrazí hodnotu prvního sloupce (i skrytého)
Na závěr
Jsem si vědom toho, že jsem po celou dobu článku ignoroval vestavěný prvek na listu Excelu, který je jakýmsi hybridem mezi ComboBoxem a ListBoxem – seznam dostupný pod kartou Data / Ověření. V podstatě představuje jednovýběrový ListBox a zaslouží si samostatnou kapitolu a článek… Padlááá.