C++Builder logo
Application Desktop Toolbar (AppBar) Application Desktop Toolbar (AppBar)
 
Even if you don't know what an Application Desktop Toolbar is, you're using at least one. The Windows taskbar is an Application Desktop Toolbar (in short an AppBar). The MSOffice Toolbar is an AppBar too.  Même si vous ne savez pas ce qu'est une Application Desktop Toolbar (en abrégé AppBar), vous en utilisez au moins une. La barre de tâche de Windows est une Application Desktop Toolbar, de même que la barre de bouton de MSOffice.
You will learn, in this article, how to create an AppBar, how to put buttons or other controls on it and how to drop files to the AppBar and create a button allowing you to open the file. Our AppBar will look like this :  Dans cet article, vous apprendrez à créer une AppBar, à placer des boutons ou d'autres contrôles et à gérer le drag and drop de façon à créer automatiquement un bouton qui permettra d'ouvrir un fichier. Notre AppBar ressemblera à ceci :


AppBar

 
What is an AppBar? Qu'est-ce qu'une AppBar?

Basicaly, an AppBar is a window that can be docked to an edge of the screen. The workarea of the screen is shrunk so that whenever you maximizes a window, that window doesn't hide any AppBar. An AppBar can be moved by dragging the AppBar client's window and docked to another edge.
 

Une AppBar est une fenêtre normale qui peut être arrimée à un des bords de l'écran. La zône utilisable de l'écran est alors diminuée de façon à ce que lorsque vous maximalisez une fenêtre, celle-ci ne se superpose pas à une AppBar. Vous pouvez bouger l'AppBar en la tirant avec la souris et éventuellement l'arrimer à un autre bord de l'écran.

How does it work? Comment faire?

When you create, remove an AppBar, set its size or position, you send messages to the AppBar using a special function SHAppBarMessage() and a structure APPBARDATA filled with the required parameters.
 

Lorsque vous créez ou supprimez une AppBar, lorsque vous changez sa taille ou sa position, vous envoyez  à l'AppBar un message particulier en utilisant la fonction SHAppBarMessage() et en remplissant la structure APPBARDATA avec les valeurs nécessaires.

How to create and remove an AppBar? Comment créer et supprimer une AppBar?

To create an AppBar, you have to register it because Windows keeps a list of all registered AppBars. This list is used to manage all AppBars.
First, create a new project. On your form, drop a TSpeedButton, a TImage , a TEdit and another TSpeedButton. Set the Height of all controls to 22. The Edit control allows us to type an URL and the second SpeedButton will launch the Browser to access the specified URL. The position of each control on the form doesn't matter because we will recalculate the position by code.
 

Lorsque vous créez une AppBar, vous devez l'enregistrer. Windows garde en mémoire une liste de toutes les AppBars créées de façon à les gérer correctement.
Ouvrez un nouveau projet. Sur le formulaire, placez un TSpeedButton, un TImage, un TEdit et un second TSpeedButton. Le TEdit nous servira à introduire une adresse Internet et le second SpeedButton nous permettra de lancer le browser pour avoir accès à cette adresse. La position de chaque composant n'a pas d'importance, nous recalculerons sa position par code.

Unit.h : 

class TForm1 : public TForm 

__published// IDE-managed Components 
    TSpeedButton *SpeedButton1; 
    TImage *Image1; 
    TEdit *Edit1; 
    TSpeedButton *SpeedButton2; 
private// User declarations 
    APPBARDATA abd;                //Structure needed by SHAppBarMessage() 
    int ControlTop;                //Used by function CalcComponentPos() 
    int ControlLeft;               //Used by function CalcComponentPos() 
    int AppBarHeight,AppBarWidth;  //The AppBar height and width depends on the 
                                   //Edge where the AppBar is docked 
    void __fastcall CreateParams(TCreateParams &Params); 
    void __fastcall WndProc(TMessage &Message); 
    void __fastcall OnMoving(TMessage &Message); //Called when we receive the 
                                                 //WM_MOVING notification message 
    void __fastcall CalcComponentPos(UINT EdgePos); 
public:  // User declarations 
    __fastcall TForm1(TComponent* Owner); 
    __fastcall ~TForm1(); 

};


As you can see in the header above, we will override the CreateParams() function of the form to remove its caption bar. We override the WndProc() function as well to respond to messages send by windows to our AppBar. Vous aurez remarqué que nous surchargeons la fonction CreateParams() de la fenêtre pour supprimer la caption bar de celle-ci. Nous surchargeons aussi la fonction WndProc() pour pouvoir répondre aux messages envoyer par Windows à notre AppBar.

Unit1.cpp : 

//User defined Callback message. 
const int WM_ABNOTIFY=WM_USER+100; 
//--------------------------------------------------------------------------- 
__fastcall TForm1::~TForm1() 

    // Unreregister the AppBar by sending the ABM_REMOVE message 
    SHAppBarMessage(ABM_REMOVE, &abd); 

//--------------------------------------------------------------------------- 
__fastcall TForm1::TForm1(TComponent* Owner) 
    : TForm(Owner) 

    //the components are placed on the AppBar depending on their index 
    Edit1->ComponentIndex=2; 
    SpeedButton2->ComponentIndex=3; 

    //Initialize the APPBARDATA structure 
    abd.cbSize = sizeof(abd); 
    abd.hWnd = Handle; 
    abd.uCallbackMessage = WM_ABNOTIFY; 

    // Register the AppBar by sending ABM_NEW message 
    SHAppBarMessage(ABM_NEW, &abd); 

    //At startup position the AppBar on Top 
    abd.uEdge=ABE_TOP; 

    //Calculate the position of each component on the AppBar 
    CalcComponentPos(abd.uEdge); 

    //Propose an edge and a position for the AppBar. 
    //Windows will adjust the rectangle if necessary (for instance, if there 
    //is already an AppBar on the chosen edge 
    SetRect(&abd.rc,0,0,Screen->Width,Screen->Height); 
    SHAppBarMessage(ABM_QUERYPOS,&abd); 

    // Calculate final size 
    abd.rc.bottom=abd.rc.top+AppBarHeight; 
    SHAppBarMessage(ABM_SETPOS,&abd); 
    // Set AppBar to the correct location 
    SetWindowPos(abd.hWnd,NULL,abd.rc.left,abd.rc.top, 
            abd.rc.right-abd.rc.left,abd.rc.bottom-abd.rc.top, 
            SWP_NOZORDER | SWP_NOACTIVATE); 

//---------------------------------------------------------------------------


Above, we have registered our AppBar sending the ABM_NEW message. Setting its size and position is not trivial. Because our AppBar can't interfere with already existing AppBars, we propose an edge and a rectangle using the ABM_QUERYPOS message. Windows will alter the position if needed. Once we have the position we calculate the size and call SHAppBarMessage() with the ABM_SETPOS message. Now, we finally have the size and position of the AppBar and we can move it to the correct location using SetWindowPos(). Ci-dessus, nous avons enregistré notre AppBar en envoyant le message ABM_NEW. A cause du fait que notre AppBar ne peut interférer avec une autre AppBar qui se trouverait déjà sur le même bord que la nôtre, nous devons proposer au shell un rectangle où nous aimerions placer l'AppBar. Ceci est réalisé en envoyant le message ABM_QUERYPOS. En retour, Windows nous renvoie la position où notre AppBar peut se trouver sans interférer avec une autre AppBar. Ensuite, on calcule la taille de l'AppBar et celle-ci est proposée au shell par le message ABM_SETPOS et enfin, nous pouvons placer notre AppBar avec la fonction SetWindowPos().

Unit1.cpp : 

//--------------------------------------------------------------------------- 
void __fastcall TForm1::CalcComponentPos(UINT EdgePos) 

    ControlTop=0; 
    ControlLeft=0; 
    AppBarWidth=0; 
    AppBarHeight=0; 
    // Get the biggest Width and Height 
    for (int i=0;i<ComponentCount;i++) 
    { 
        TControl* Control = dynamic_cast<TControl*>(Components[i]); 

        if(Control) 
        { 
            // Get the greatest width and height 
            // This will determine the thickness of the appbar 
            if(Control->Height>AppBarHeight) 
                AppBarHeight=Control->Height; 
            if (Control->Width>AppBarWidth) 
                AppBarWidth=Control->Width; 
        } 
    } 
    AppBarWidth+=6; 
    AppBarHeight+=6; 
    //Calc Top and left properties 
    for (int i=0;i<ComponentCount;i++) 
    { 
        TControl* Control = dynamic_cast<TControl*>(Components[i]); 

        if(Control) 
        { 
            if((EdgePos==ABE_LEFT)||(EdgePos==ABE_RIGHT)) 
            { 
                Control->Left=(AppBarWidth-Control->Width)/2; 
                Control->Top=ControlTop+1; 
                ControlTop=Control->Height+Control->Top; 
            } 
            if((EdgePos==ABE_TOP)||(EdgePos==ABE_BOTTOM)) 
            { 
                Control->Top=(AppBarHeight-Control->Height)/2; 
                Control->Left=ControlLeft+1; 
                ControlLeft=Control->Width+Control->Left; 
            } 
        } 
    } 

//---------------------------------------------------------------------------


The CalcComponentPos() function doesn't need a lot of explanation. We first iterate through all the components on our form to get the biggest Height and Width. This is needed to determine the size of our AppBar. We then iterate through the components to set the Top and Left properties of each component so that they will always be well positionned depending on the edge. La fonction CalcComponent() est assez simple. Elle permet de déterminer la taille de notre AppBar. Celle-ci varie d'après le bord choisi. Dans le cas du bord droit par exemple, la largeur de notre AppBar doit au moins être égale à la largeur du plus grand composant. Dans le cas du bord supérieur, la hauteur de l'AppBar doit être égale au moins à la hauteur la plus grande des composants. Ensuite, on calcule pour chaque composant ses propriétés Left et Top.

Unit1.cpp : 

//--------------------------------------------------------------------------- 
void __fastcall TForm1::WndProc(TMessage &Message) 

    switch (Message.Msg) 
    { 
        case WM_ABNOTIFY: 
            switch (Message.WParam) 
            { 
                case ABN_WINDOWARRANGE: 
                    break
                case ABN_STATECHANGE: 
                    break
                case ABN_FULLSCREENAPP: 
                    if (Message.LParam) 
                        SetWindowPos(Handle,HWND_BOTTOM,0,0,0,0, 
                            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); 
                    else SetWindowPos(Handle,HWND_TOPMOST,0,0,0,0, 
                            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); 
                    break
                case ABN_POSCHANGED: 
                    SHAppBarMessage(ABM_QUERYPOS,&abd); 
                    switch (abd.uEdge) 
                    { 
                        case ABE_LEFT: 
                            abd.rc.right=abd.rc.left+AppBarWidth; 
                            break
                        case ABE_TOP: 
                            abd.rc.bottom=abd.rc.top+AppBarHeight; 
                            break
                        case ABE_RIGHT: 
                            abd.rc.left=abd.rc.right-AppBarWidth; 
                            break
                        case ABE_BOTTOM: 
                            abd.rc.top=abd.rc.bottom-AppBarHeight; 
                            break
                    } 
                    SHAppBarMessage(ABM_SETPOS,&abd); 
                    CalcComponentPos(abd.uEdge); 
                    MoveWindow(abd.hWnd,abd.rc.left,abd.rc.top, 
                                abd.rc.right-abd.rc.left, 
                                abd.rc.bottom-abd.rc.top,TRUE); 
                    break
            } 
        case WM_ACTIVATE: 
            SHAppBarMessage(ABM_ACTIVATE,&abd); 
            Message.Result=true
            break
        case WM_MOVING: 
            OnMoving(Message); 
            Message.Result=true
            break
        case WM_EXITSIZEMOVE: 
            SHAppBarMessage(ABM_QUERYPOS,&abd); 
            SHAppBarMessage(ABM_SETPOS,&abd); 
            Message.Result=true
            break
        case WM_WINDOWPOSCHANGED: 
            SHAppBarMessage(ABM_WINDOWPOSCHANGED,&abd); 
            Message.Result=true
            break
        default
            TForm::WndProc(Message); 
    } 

//---------------------------------------------------------------------------


Here, we have overridden the WndProc() method of a form to respond to special notification messages sent by windows to our AppBar. These are sent when the state, the size or the position of the AppBar changes or when an application is launched in full-screen mode. See the online help for details. The most important is  the ABN_POSCHANGED. It is sent when our AppBar or another one has changed its size or position. The ABN_STATECHANGE is sent when the state of the AppBar has changed. The ABN_FULLSCREENAPP is sent when a full-screen App is launched or closed. 
When we drag our AppBar we receive a WM_MOVING message. See the code below :
Windows envoie à notre AppBar certains messages. Pour traiter ces messages, nous surchargeons la méthode WndProc() de notre fenêtre. ces messages sont envoyés pour signaler que l'état (ABN_STATECHANGE), la taille ou la position (ABN_POSCHANGED) de l'AppBar a changé ou bien qu'une application a été lancée en mode plein-écran ou qu'on a fermé une application qui était en mode plein-écran (ABN_FULLSCREENAPP). 
lorsque nous déplaçons notre AppBar, nous recevons le message WM_MOVING. Voir le code ci-dessous :

Unit1.cpp : 

//--------------------------------------------------------------------------- 
void __fastcall TForm1::OnMoving(TMessage &Message) 

    //Thanks to Damon Chandler who provided a sample on how to use the 
    //WM_MOVING message. I didn't find a more clear method so I reused his 
    //code with nearly no change 

    // Get mouse position 
    POINT MousePos; 
    GetCursorPos(&MousePos); 

    // Create 4 Rectangles corresponding to the edges of the screen. 
    // If the mouse is in one of these Rect, we have to change the uEdge 
    // parameter of the APPBARDATA structure, telling the appbar where 
    // it has to dock 
    RECT Desktop= Rect(0,0,Screen->Width,Screen->Height); 
    RECT LeftRect=Rect(Desktop.left,Desktop.top,Desktop.left+AppBarWidth, 
                        Desktop.bottom); 
    RECT RightRect=Rect(Desktop.right-AppBarWidth,Desktop.top, 
                            Desktop.right,Desktop.bottom); 
    RECT TopRect=Rect(Desktop.left,Desktop.top,Desktop.right, 
                        Desktop.top+AppBarHeight); 
    RECT BottomRect=Rect(Desktop.left,Desktop.bottom-AppBarHeight, 
                            Desktop.right,Desktop.bottom); 

    if(PtInRect(&LeftRect,MousePos)) 
    { 
        abd.rc=LeftRect; 
        abd.uEdge=ABE_LEFT; 
    } 
    else if(PtInRect(&RightRect,MousePos)) 
    { 
        abd.rc=RightRect; 
        abd.uEdge=ABE_RIGHT; 
    } 
    else if(PtInRect(&TopRect,MousePos)) 
    { 
        abd.rc=TopRect; 
        abd.uEdge=ABE_TOP; 
    } 
    else if(PtInRect(&BottomRect,MousePos)) 
    { 
        abd.rc=BottomRect; 
        abd.uEdge=ABE_BOTTOM; 
    } 

//---------------------------------------------------------------------------


When receiving the WM_MOVING message, we create 4 rectangles and check if the mouse is in one of them, changing, accordingly, the APPBARDATA parameters. Lorsqu'on reçoit le message WM_MOVING, on crée 4 rectangles correspondant aux 4 bords de l'écran et on teste si la souris se trouve dans l'un de ces rectangles. Si c'est le cas, les paramètres de la structure APPBARDATA sont modifiés en conséquence.
 
Removing the form caption : Suppression de la caption bar de la fenêtre :

Our AppBar is functional, but how to get rid of the caption bar? 
To remove the caption bar of the form, we will override the CreateParams() method of the form and put some code in the OnMouseDown event of the form allowing us to move the AppBar when clicking in the client area.
 

Notre AppBar est maintenant fonctionnelle. Il nous reste à nous débarasser de la caption bar de la fenêtre. On réalise ceci en surchargeant la méthode CreateParams() de la fenêtre. Pour pouvoir bouger l'AppBar en cliquant dans la zône client de la fenêtre, on va insérer quelques lignes de code dans l'événement OnMouseDown de la fenêtre.

Unit1.h : 

__published// IDE-managed Components 

     void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button, 
              TShiftState Shift, int X, int Y)

Unit1.cpp : 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::CreateParams(TCreateParams &Params) 

    TForm::CreateParams(Params); 
    Params.Style= Params.Style &(~(WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX)); 
    // remove the caption 
    Params.Style |= WS_POPUP; 
    Params.Style ^= WS_DLGFRAME; 

    Params.ExStyle= Params.ExStyle &(~WS_EX_APPWINDOW); 
    Params.ExStyle= Params.ExStyle | WS_EX_TOOLWINDOW; 

//--------------------------------------------------------------------------- 
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, 
      TShiftState Shift, int X, int Y) 

    ReleaseCapture(); 
    Perform(WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0); 

//---------------------------------------------------------------------------


Finally, put the code below in the OnClick event of the second SpeedButton to be able to launch your browser with the address specified in the TEdit control. La dernière chose à faire est de lancer le browser avec l'adresse spécifiée dans le composant TEdit lorsqu'on clique sur le second SpeedButton. Placez le code ci-dessous dans l'événement OnClick du second SpeedButton.

Unit1.h : 

__published// IDE-managed Components 
    ... 
    void __fastcall SpeedButton2Click(TObject *Sender); 
 

Unit1.cpp : 
//--------------------------------------------------------------------------- 

void __fastcall TForm1::SpeedButton2Click(TObject *Sender) 

    if((int)ShellExecute(Handle,"open",Edit1->Text.c_str(), 
        NULL,"c:\\winnt",SW_SHOW)<32) 
        ShowMessage("Unable to launch your browser"); 

//---------------------------------------------------------------------------


Note : Remarque :

Some features of AppBars are not used here. For instance, the auto-hide feature or the always-on-top feature. See the online help or Microsoft MSDN if you want to implement these features.
 

Certaines caractéristiques des AppBars ne sont pas utilisées ici comme, par exemple, le mode 'auto-hide' ou 'always-on-top'. Pour en savoir plus, consultez l'aide en ligne ou la bibliothèque Microsoft MSDN.

Dropping files and creating buttons : Gérer le drag and drop et création de boutons :

One of the nice usage of AppBars is the ability to put buttons on it to quickly launch applications. I'll show you below how to drop files on the AppBar and how to automaticaly create a button to launch that file.
 

Vous pouvez utiliser une AppBar comme toolbar et y placer des boutons pour lancer rapidement différentes applications. Ci-dessous, vous trouverez le code nécessaire pour faire un drag and drop d'un fichier sur la AppBar et pour créer automatiquement un bouton permettant d'ouvrir ce fichier.

private// User declarations 

    int ButtonCount; 
    TStringList *ButtonApp; 

    void __fastcall OnDropFiles(TMessage &Message); 
    void __fastcall DraggedButtonClick(TObject *Sender); 

Unit1.cpp : 

In the Constructor : 

    ... 
    ButtonCount=0; 
    ButtonApp=new TStringList; 
    // Allow dragging file 
    DragAcceptFiles(Handle,true); 

In the destructor : 

    delete ButtonApp; 
    DragAcceptFiles(Handle,false); 
    ... 

In the WndProc() : 

    ... 
    case WM_DROPFILES: 
        OnDropFiles(Message); 
        Message.Result=true
        break
    default
        TForm::WndProc(Message); 
  

//--------------------------------------------------------------------------- 
void __fastcall TForm1::OnDropFiles(TMessage &Message) 

    char *FileName; 
    SHFILEINFO FileInfo; 

    UINT NumberOfFiles=DragQueryFile((HDROP)Message.WParam,0xFFFFFFFF,NULL,MAX_PATH); 
    if (NumberOfFiles>1)ShowMessage("Select only one file at a time!"); 
    int PathLength=DragQueryFile((HDROP)Message.WParam,0,NULL,0); 
    FileName=new Char[PathLength+1]; 
    DragQueryFile((HDROP)Message.WParam,0,FileName,PathLength+1); 

    //Get the icon associated with the file 
    if(SHGetFileInfo(FileName,0,&FileInfo,sizeof(FileInfo),SHGFI_ICON)) 
    { 
        Graphics::TIcon *Icon=new Graphics::TIcon(); 
        Graphics::TBitmap *Bitmap=new Graphics::TBitmap(); 
        TImageList *ImageList=new TImageList(NULL); 
        ImageList->BkColor=clNone; 
        TSpeedButton *SpeedButton=new TSpeedButton(this); 
        Icon->Handle=FileInfo.hIcon; 
        int Index=ImageList->AddIcon(Icon); 
        ImageList->GetBitmap(Index,Bitmap); 
        SpeedButton->Parent=this
        SpeedButton->Glyph->Assign(Bitmap); 
        SpeedButton->Flat=false
        SpeedButton->Transparent=false
        SpeedButton->Caption=""
        SpeedButton->Width=22; 
        SpeedButton->Height=22; 
        SpeedButton->Tag=ButtonCount; 
        SpeedButton->OnClick=DraggedButtonClick; 
        ButtonApp->Add(FileName); 
        delete Icon; 
        delete Bitmap; 
        delete ImageList; 
    } 
    // needed to avoid that the form gets its original width and 
    // height again. 
    Perform(WM_ABNOTIFY,ABN_POSCHANGED,0); 
    delete FileName; 
    ButtonCount++; 
    DragFinish((HDROP)Message.WParam); 

//--------------------------------------------------------------------------- 
void __fastcall TForm1::DraggedButtonClick(TObject *Sender) 

    TSpeedButton* SB = dynamic_cast<TSpeedButton*>(Sender); 

        if(SB) 
        { 
            if((int)ShellExecute(Handle,"open",ButtonApp->Strings[SB->Tag].c_str(), 
                NULL,NULL,SW_SHOW)<32) 
                ShowMessage("Unable to launch your browser"); 
        } 

//---------------------------------------------------------------------------


The DragAcceptFiles() is telling our AppBar to accept dropped files.  In the OnDropFiles(), we first get the filename using DragQueryFile().The filename is saved in a TStringList. Then, we get the icon associated with the file. The ImageList is used to transform an icon to a bitmap because the TSpeedButton glyph property is of type TBitmap. When we have the icon, we create the SpeedButton and set its event OnClick to our function DraggedButtonClick(). The Tag property of the SpeedButton is used to differentiate two buttons. We then send the ABN_POSCHANGED message to repaint the AppBar.. La fonction DragAcceptFiles() est utilisée pour spécifier que notre AppBar peut recevoir des fichiers que l'on 'droppe' sur elle. Dans la fonction OnDropFiles(), nous récupérons d'abord le nom et le path du fichier. Celui-ci est sauvegardé dans un TStringList. Ensuite, on récupère l'icône associée à ce fichier et on crée un bouton pour ouvrir ce fichier. Le TImageList est utilisé pour convertir une icône en bitmap. En effet, la propriété Glyph d'un SpeedButton est du type TBitmep. L'événement OnClick du SpeedButton pointe vers notre fonction DraggedButtonClick(). La propriété Tag sert à différencié les SpeedButtons entre eux. Finalement, on envoie à l'AppBar le message ABN_POSCHANGED pour que le nouveau bouton soit bien positionner dans l'AppBar.

You have to provide a way to save the parameters and filename of each button in an .ini file or in the registry. No code is given here to do this. Vous devez, vous même, trouver un moyen de sauver dans un fichier .ini ou dans la base de registre les paramètres et le nom du fichier pour chaque bouton créé de façon à pouvoir relancer l'application avec les boutons créés précédemment. Aucun code n'est donné ici pour réaliser cela.

Complete project source code (BCB4) : AppBarsrc.zip Source complète du projet (BCB4) : AppBarsrc.zip