Kalendarz OCX   strona główna:
A po co ten Excel ;-)
 
    Niby nie dotyczy to Excela - nie. Najczęściej zdarza się nam korzystać z kontrolek a nie je stwarzać ale cóż... Mało jest przykładów  
wykorzystywania najróżniejszych "nie Excelowych" rzeczy do realizacji zadań w Excelu ??   kontrolke można
pobrać bezpośrednio z:
 - API i wykorzystanie funkcji zdefiniowanych w zewnętrznych bibliotekach dll  
 - Dodatki automatyzacji (dll)  
 - kontrolki ActiveX (ocx)   kalendarz1.ocx
Wykorzystujemy je ale czy nie warto byłoby móc tworzyć własne a następnie wykorzystywać je w Excelu? Nie wspomnę już o fakcie, że  
żaden kierunek poszerzania wiedzy nie jest przecież czasem straconym i zapewne kiedyś się przyda.  
    Przykładowe dodatki automatyzacji już kiedyś pisałem tym razem przyszła pora na próbę ugryzienia OCX. :-)  
 
    Tematem tego artykułu będzie utworzenie kontrolki ActiveX w VB6 (Microsoft Visual Studio 6.0) i jej wykorzystanie e Excelu.  
Kontrolką tą będzie Kalendarz.   
 
Główne zadania z którymi trzeba będzie się zmierzyć to:  
 - Utworzenie formularza oraz elementów składających się na kalendarz w VB6  
 - Utworzenie publicznej tablicy kontrolek których zdarzenie kliknięcia będziemy przechwytywać z mod. Class  
 - Dynamiczna zmiana właściwości Caption i BackColor elementów kalendarza względem wybranego miesiąca  
 - Utworzenie i obsługa właściwości Value naszej kontrolki.  
 - Definiowanie zdarzenia DateClick kontrolki i jego wywołanie obsługiwane z mod. Class  
 - Rejestracja kontrolki w systemie użytkownika  
 - Przykłady wykorzystania  
 
Utworzenie Formy  
A więc od początku :-)  
 - Odpalamy VB6.   
 - W oknie New Project wybieramy: ActiveX Control  
 - Zakładka: Project / Project1 Properties... / Zakładka: General / Nadajemy własną: Project Name. Tu Kalendarz1   Make Your First ActiveX Control
    To właśnie po tej nazwie (i nazwie formularza) będziemy szukać naszej kontrolki chcąc dodać własny komponent ActiveX w Excelu.  
 - Prawy Prz. Myszy na Formie / Properties / Nadajmy nazwę formy: Właściwość (Name). Tu mojKalendarz  
Dotychczasową część zadania można podejrzeć w lekcjach 1 - 4 na stronie z linku po lewej  
 
    Nasz kalendarz będzie wyglądał następująco (od góry)
 
 - Dwa CommandButtony (Command1 i Command2) rozdzielone Labelem (labData) żeby wybierać  
   miesiąc i rok. Jakoś tak się złożyło że "w lewo" to Command2 :-P  
   Strzałki w Caption takich Button'ów osiągam poprzez zastosowanie czcionki Wingdings3  
   "w lewo" - t ; "w prawo" - u :-)  
 - W ramce (bez Caption) umieszczam 7 Labeli (Pn-N) określając ich właściwości:  
   Alignment 2-Center, ForeColor w zależności od dnia tygodnia, Font -> Pogrubiona  
   Pod labelami umieszczam 42 CommandButton'y (po 7 w 6'ściu rzędach). Właściwości:  
   (Name) CBi (dla i = 1 do 42 licząc od LewegoGórnego rogu rzędami)  
   Style 1 - Graphical Jest to potrzebne ponieważ będziemy zmieniać BackColor tych Button'ów  
                              w zależności od danego dnia tygodnia. Gdyby pominąć ustawienie tej   
                              właściwości zmiana właściwości BackColor kontrolek wywołana po zmianie  
                              miesiąca nie przyniosło by efektu.  
 - na samym dole TextBox Text1. Enabled = False. Będzie on przechowywał wartość aktualnie wybranej daty. (domyślnie Date())  
 
   Całość ma działać tak: Po załadowaniu kontrolki kalendarz ma przedstawiać bieżący miesiąc. labData.Caption ma wskazywać na np.:  
grudzień 2010. Buttony będące elementami kalendarza: Mają być widoczne tylko te które będą przedstawiać dni ze wskazanego mie-  
siąca i ich układ ma odwzorowywać układ dni w miesiącu np.: 2010-12-01 to Środa więc pierwszą widoczną kontrolką ma być CB3 itd.  
Buttony mają mieć ustawiony BackColor w zależności do dnia miesiąca: Pn-Pt biały, So - Szary, N - j. Czerwony. Kliknięcie dowolnego  
(widocznego) buttona ma uruchomić zdarzenie DateClick które będzie można oprogramować w aplikacji w której użyjemy kontrolki.  
Po kliknięciu zostanie zmieniona również właściwość Value naszej kontrolki jak również kliknięta data ma być wyświetlona w TextBox'ie  
Text1 pod kalendarzem. Wartość właściwości Value tuż po załadowaniu kontrolki ma być = Date().  
 
Oprogramowanie elementów kontrolki.  
 
Zadaniem realizowanym po załadowaniu Formy jak również po zmienia miesiąca jest ustawienie kalendarza. Mają być widoczne tylko te   
Button'y w ramce, które będą reprezentowały dzień wybranego miesiąca. Ich układ ma przedstawiać układ dni w miesiącu oraz BackColor   
ma być odpowiedni. Napisałem procedurę realizującą to zadania. Procedura w zależności od daty przekazanej do niej w argumencie,  
ustawia co i jak trzeba.  
 
Sub UstawKalendarz(dData As Date)  
    Dim objCtr As CommandButton, iCtr As Byte  
    Dim bMiesiac As Byte, iRok As Integer, dPierwszy As Date  
    Dim dTaData As Date, bFlag As Boolean  
      
    Const lColorPP As Long = &HFFFFFF     'biały  
    Const lColorSo As Long = &H8000000F   'szary  
    Const lColorN As Long = &H8080FF       'j. czerwony  
    Const lColorDzis As Long = &HFFFFC0    'j.niebieski  
      
    bMiesiac = Month(dData)  
    iRok = Year(dData)  
    dPierwszy = DateSerial(iRok, bMiesiac, 1)  
      
    For iCtr = 1 To 42  
        dTaData = dPierwszy + iCtr - Weekday(dPierwszy, vbMonday)  
          
        Set objCtr = UserControl.Controls("CB" & iCtr)  
        With objCtr  
            .Caption = Day(dTaData)  
            bFlag = Month(dTaData) = Month(dData)  
            .Visible = bFlag  
            If bFlag Then  
                Select Case Weekday(dTaData, vbMonday)  
                 Case 1 To 5: .BackColor = lColorPP  
                 Case 6: .BackColor = lColorSo  
                 Case 7: .BackColor = &H8080FF  
                End Select  
                If dTaData = Date Then  
                    .BackColor = lColorDzis  
                End If  
            End If  
            .Tag = CLng(dTaData)  
        End With  
        Set objCtr = Nothing  
    Next  
    UserControl.labData.Caption = Format(dData, "mmmm yyyy")  
End Sub  
 
Załóżmy że mamy grudzień 2010. Data przekazana do procedury to 2010-12-01.  
bMiesiac = 12; iRok = 2010; dPierwszy = 2010-12-01; Weekday(dPierwszy, vbMonday) = 3 (Środa)  
Jaka data powinna być e Buttonie CB1?? dPierwszy - 3 + 1 :-) Stąd dTaData będąca datą dla danej kontrolki można określić jako:  
 
        dTaData = dPierwszy + iCtr - Weekday(dPierwszy, vbMonday)  
 
Jeżeli miesiąc daty dTadata = miesiącowi dData kontrolka ma być widoczna a jej BackColor odpowiednio sformatowany. Każda   
kontrolka we właściwości Tag dostaje liczbę reprezentującą datę którą przedstawia. Będzie nam łatwiej określić datę klikniętego buttona   
właśnie po tej wartości.  
 
    Teraz. Które kontrolki miałyby wywoływać tą procedurę? Przyciski "w lewo" i "w prawo" określające miesiąc.  
 
Private Sub Command1_Click()  
    Dim dData As Date  
      
    dData = DateValue(UserControl.labData.Caption)  
    UstawKalendarz DateSerial(Year(dData), _  
                              Month(dData) + 1, _  
                              1)  
End Sub  
 
Private Sub Command2_Click()  
    Dim dData As Date  
      
    dData = DateValue(UserControl.labData.Caption)  
    UstawKalendarz DateSerial(Year(dData), _  
                              Month(dData) - 1, _  
                              1)  
End Sub  
 
Procedurę tą wywołamy również w UserControl_Initialize z datą Date  
Jednak w tem zdarzeniu będziemy musieli napisać trochu więcej niż tylko odwołanie do procedury ustawiającej kalendarz.  
 
W zdarzeniu UserControl_Initialize będziemy misiec stworzyć tablicę kontrolek których zdarzenie kliknięcia będziemy chcieli przechwycić i   
obsłużyć jedną procedurą. Tak jest wygodniej gdyż nie trzeba oprogramować zdarzenia Click każdej kontrolki z osobna ale technika   
stwarza inne problemy o których za chwilę. Na razie utworzymy mod. Class (Zakładka: Project / Add Class Module) i nazwiemy go   
clsCBEvents. W jego kodzie umieścimy  
 
Public WithEvents objCmdButton As CommandButton  
 
w kodzie naszej kontrolki  
   w części deklaracji:  
 
Dim tblCmdButtons(1 To 42) As New clsCBEvents  
Dim dValue As Date  
 
  oraz  
 
Private Sub UserControl_Initialize()  
    Dim iCtr As Byte  
      
    With UserControl  
        For iCtr = 1 To 42  
            Set tblCmdButtons(iCtr).objCmdButton = .Controls("CB" & iCtr)  
        Next  
        .labData.Caption = Format(Date, "mmmm yyyy")  
        dValue = Date  
        .Text1 = dValue  
    End With  
      
    UstawKalendarz Date  
End Sub  
 
Private Sub UserControl_Terminate()  
    Erase tblCmdButtons  
End Sub  
 
Zadeklarowaliśmy tablicę na 42 kontrolki. I wypełniamy ją wszystkimi elementami kalendarza.  
    Po takim zabiegu w mod. clsCBEvents będziemy mogli oprogramować zdarzenie objCmdButton_Click będące kliknięciem na dowolny   
Button reprezentujący dzień na kalendarzu.  
 
Właściwość Value kontrolki.  
 
Po co nam to? Jeżeli naszą kontrolkę osadzimy na UserForm'ie to chcąc np.: z przycisku sprawdzić jaką datę wskazuje nasza kontrolka   
będziemy potrzebować właśnie właściwości której wartość określimy już w momencie kliknięcia na przycisk dowolnego dnia na naszej   
kontrolce. Musimy również zaznaczyć, że wartość tej właściwości tuż po załadowaniu kontrolki będzie = Date. Jak więc utworzyć i   
określać właściwość Value naszej kontrolki.  
   W kodzie kontrolki mamy Dim dValue As Date  potrzebne jest jeszcze oprogramowanie zdarzeń: UserControl_WriteProperties i   
_ReadProperties oraz procedury określające wartość właściwości Value względem zmiennej dValue  
 
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)  
    PropBag.WriteProperty Name:="Value", _  
                          Value:=dValue  
End Sub  
 
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)  
    dValue = PropBag.ReadProperty(Name:="Value", _  
                                  DefaultValue:=Date)  
End Sub  
 
Public Property Get Value() As Date  
    Value = dValue  
End Property  
 
Public Property Let Value(ByVal New_Value As Date)  
    dValue = New_Value  
    PropertyChanged PropertyName:="Value"  
    UserControl.Text1 = Format(dValue)  
End Property  
 
Utworzenie zdarzenia DateClick  
 
Utworzenie zdarzenia będzie nam potrzebne przez wzgląd na dwa przypadki.   
   1. Gdy nasza kontrolka zostanie wstawiona do Arkusza Excela to chcąc wstawić klikniętą datę do jakiejś komórki musimy umieć   
       zareagować odpowiednio na to zdarzenie. Gdyby nie określić zdarzenia można by zczytywać Me.mojKalendarz.Value ale kiedy?   
       No właśnie :-) Informacja o dokonaniu wyboru daty powinna wyjść z naszej kontrolki i być możliwa do przechwycenia w Aplikacji   
       klienckiej gdzie określimy miejsce docelowe dla daty która będzie argumentem zdarzenia.  
   2. Gdy nasza kontrolka znajduje się na UserForm'ie oprogramowując to zdarzenie możemy przekazywać wartość arg. Tego zdarzenia    
       (dDate) w miejsce przeznaczenia dla tej daty. Np.: TextBox na tym UserForm'ie  
 
Utworzenie zdarzenia kontrolki to zadeklarowanie tego zdarzenia w kodzie class'y tej kontrolki. Piszemy więc:  
 
Public Event DateClick(dDate As Date)  
 
Jednak zdarzenie jest zadeklarowane w kodzie mod. Class kontrolki a RaiseEvent (wywołanie zdarzenia) powinno być następstwem   
zdarzenia objCmdButton_Click do którego mamy dostęp w mod. Class clsCBEvents.Przyznam, że to mnie przez chwile zatrzymało :lol:   
Napisałem to tak: W kodzie kontrolki napisałem maleńką procedurkę  
 
Public Sub RaiseEventSub(dDate As Date)  
    RaiseEvent DateClick(dDate)  
End Sub  
 
A zdarzenie objCmbButton_Click w mod. clsCBEvents napisałem następująco  
 
Private Sub objCmdButton_Click()  
    With objCmdButton.Parent  
        .Value = CDate(objCmdButton.Tag)  
        '          Use the CallByName function (VB6 only).  
        ' http://www.vb-helper.com/howto_call_routine_by_name.html   Use the CallByName function
         CallByName objCmdButton.Parent, "RaiseEventSub", VbMethod, .Value  
    End With  
End Sub  
 
Utworzenie kontrolki OCX  
 
Jak już wszystko jest j.w...   Make Your First ActiveX Control
Zakładka: File / Save Project.. Co pozwoli nam, jeżeli zaistnieje taka potrzeba, koniecznych zmian  
Zakładka: File / Make Kalendarz.ocx... Co utworzy kontrolkę we wskazanym miejscu oraz zarejestruje kontrolkę w systemie.  
(obrazkiem powyższego w linku po lewej)  
 
Wcześniej możemy jeszcze nadać naszej kontrolce jej własną ikonę którą będziemy widzieć na ToolBox'ie :-)  
 - zróbmy fotę naszej kontrolki, zmniejszmy ją odpowiednio i zapiszmy jako jpg  
 - o oknie Project, PPM na mod. Kontrolki. Odnajdźmy właściwość ToolBoxBitMap i wskażmy naszą fotę :-)  
 
Rejestracja kontrolki  
 
My już mamy zarejestrowaną ale użytkownik będzie musiał sobie naszą kontrolkę zarejestrować.   Rejestrowanie formantu ActiveX (.ocx) ręcznie
Używa się do tego programu Microsoft Register Server (Regsvr32.exe).   
Należy wywołać poprzez Menu Start / Uruchom    
regsvr32 c:\Kalendarz.ocx  
A więc regsvr32 oraz pełna ścieżka do kontrolki. (więcej w linki po lewej)  
 
Przykłady Wykorzystania  
 
1. Jako kalendarz w Arkuszu widoczny po kliknięciu PPM na kol.D i wstawiający wybraną datę w aktywną komórkę.  
 - Włączmy Excela i klikamy na ikonę Więcej formantów  
 - Na liście odnajdujemy naszą kontrolkę: Kalendarz.mojKalendarz  i rysujemy ją na Arkuszu.  
 - W kodzie Arkusza piszemy  
 
Option Explicit  
 
Private Sub mojKalendarz1_DateClick(dDate As Date)  
    ActiveCell = dDate  
End Sub  
 
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)  
    If Target.Cells(1).Column <> 4 Then Exit Sub  
    Cancel = True  
      
    With Me.mojKalendarz1  
        .Top = Target.Top  
        .Left = Target.Left + Target.Cells(1).Width  
        .Visible = True  
    End With  
End Sub  
 
Private Sub Worksheet_SelectionChange(ByVal Target As Range)  
    Me.mojKalendarz1.Visible = False  
End Sub  
 
2. Jako kalendarz na UserForm'ie którego właściwość Value podana będzie w MsgBox'ie po kliknięciu na przycisk   
- Tworzymy UserForm i na ToolBox'ie klikamy PPM / Additional Controls..  
- odnajdujemy i zaznaczamy CheckBox' przez nazwie naszej kontrolki -> OK. Ikona naszej kontrolki pojawi się na ToolBox'ie  
- wstawiamy kontrolkę na UserForm a obok przycisk którego kliknięcie oprogramowujemy  
 
Option Explicit  
 
Private Sub CommandButton1_Click()  
    MsgBox Me.mojKalendarz1.Value  
End Sub  
 
 
Niedoskonałości  
To mój pierwszy ocx i nie mam w tym temacie żadnego doświadczenia. Jednak nie znam innego sposobu na naukę jak próby więc próbuję :-)  
 - pierwsze wywołanie UserForm'a skutkuje informacją że:  
This application is about to initialize ActiveX controls that might by unsafe. If you trust the source od this file, select OK. and   
the controls will be initialized using your current workspace settings.  
 
 - w przykładzie 1. Po PPM w kol.D pojawia się Kalendarz ale pierwsze jego kliknięcie nie powoduje wstawienia daty w ActiveCell gdyż dopiero przenosi   
   Focus na kontrolkę :-|