Press "Enter" to skip to content

Icone e Toolstrip – C# GDI+

Di Solito le Toolstrip hanno icone standard da 16 x 16 pixel, ma con l’aumentare della risoluzione dei monitor a parità di dimensione dello schermo, i pixel divengono sempre più piccoli, e le icone poco visibili. In questo piccolo progetto che integrerà le librerie standard DNW aggiungendovi un paio di classi, vedremo come estrarre da una risorsa di tipo .ico la bitmap della dimensione desiderata alla risoluzione migliore e come aggiornare a runtime una ImageList cambiandone la dimensione delle icone per poi assegnarle ad una ToolStrip standard.

Questo articolo deriva da una necessità reale, scaturita la scorsa settimana quando presentando ai miei colleghi una nuova applicazione, mi sono sentita dire che non si capiva cosa ci fosse sulle icone.

Ciò è davvero una cosa brutta, visto che le icone grafiche sono la cosa su cui perdo più tempo dopo quello dedicato allo sviluppo.

A questo punto mi sono chiesta se sono io che non so fare le icone oppure se i miei colleghi sono ciechi. Temo che la somma delle due cose sia la risposta, e quindi è davvero deleterio.

Ho deciso quindi, visto che è possibile farlo, di trovare il modo per far si che gli utenti possano scegliere la dimensione dei bottoni delle toolbar in base alla risoluzione dello schermo che hanno a disposizione e soprattutto in base al grado di cecità :D.

Avevo scritto un mini articolo sul blog, registrando del codice trovato con parecchia difficoltà su internet un paio d’anni fa, per poter recuperare le icone della corretta grandezza da un oggetto Icon, ma ho scoperto con orrore, che quel metodo ha un difetto, ovvero crea dei bitmap a 8Bit veramente brutti e non estrae le bellissime icone con trasparenze disponibili adesso.

Ho fatto qualche ricerca su internet senza trovare una soluzione specifica, poi ho scoperto su MSDN che c’era una soluzione così ovvia che non era necessario cercarla, forse per questo nessuno ne ha mai parlato. C’è un metodo apposito sulla classe Icon. Si chiama ToBitmap, unendo questo metodo al codice scritto sul mio articolo invece di usare l’HBitmap, si ottiene un risultato notevolmente più bello.

Detto questo, ho creato due piccole classi helper che possono essere inserite nelle nostre librerie di codice General Purpouse e ci permettono di ottenere ciò che cercavo, dar modo agli utenti di non divenire ciechi anche se hanno acquistato un monitor a 3000 per 3000 pixel.

Anche se il mio collega Luca ha suggerito non a torto di attivare l’uso del Magnifier, tool gratuito e perfetto allo scopo. 😛 Ok finiamola con gli scherzi e passiamo al codice.

La classe GraficHelper

In questa classe helper quindi Statica troverà posto un solo metodo, generico che permetterà di gestire gli errori e generare un immagine che ci indica il nome di una eventuale risorsa mancante.

public static class GraficHelper
    {
        private static readonly string mClassName = 
            System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
          //...
    }

Nel codice della classe, aggiungiamo il nostro metodo:

public static Bitmap WriteOnBitmap(string pMessage, Bitmap pOrgBitmap, 
               Size pNewSize, Font pFont, Brush pBrush, Point pLocation)
        {
            try
            {
                Bitmap bmp = new Bitmap(pOrgBitmap, pNewSize);
                Graphics grfx = Graphics.FromImage(bmp);
                grfx.DrawString(pMessage, pFont, pBrush, pLocation.X, pLocation.Y);
                return bmp;

            }
            catch (Exception ex)
            {
                throw new ApplicationException(" " + mClassName + "."
                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
            }
        }

Questo metodo, scrive su di un bitmap una qualsiasi stringa, noi lo utilizzeremo per generare un bitmap di errore con cui comunicare al programmatore se si fosse dimenticato di aggiungere un icona inserita in una ImageList fra le risorse del programma.

La classe BtnManager

Veniamo alla classe più interessante, la classe BtnManager, che fornisce tutti i servizi di base necessari a creare la nostra applicazione come desiderato.

public static class BtnManager
    {

            private static readonly string mClassName = 
              System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
            //....
    }

Partiamo con il metodo di gestione errore, se un immagine contenuta nell’image list di origine non fosse presente come icona nelle risorse del progetto, generiamo un’icona di errore su cui scriviamo il nome dell’icona non trovata.

private static Bitmap GetNotFoundImage(string pImageName, int pSize)
        {
            try
            {
                Icon icn = new Icon(Properties.Resources.NotFound, new Size(pSize, pSize));

                Bitmap bmp = icn.ToBitmap();
                Font fnt = new Font("System", 7);
                Bitmap img = GraficHelper.WriteOnBitmap(pImageName, bmp, _
                  bmp.Size, fnt, Brushes.Black, new Point(1, 1));
                                return img;
            }
            catch (Exception ex)
            {
                throw new ApplicationException(" " + mClassName + "."
                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
            }
        }

Per fare questo abbiamo inserito un icona contenente un immagine standard con scritto Non Trovata, che useremo come base per scrivere il nome dell’icona mancante.

Il secondo metodo che implementiamo serve per generare un immagine di una certa dimensione partendo da un’icona.

public static Bitmap GetImageFromIcon(int pSize, string pName, ResourceManager pResx)
        {
            try
            {
                Icon icn = new Icon((Icon)pResx.GetObject(pName), new Size(pSize, pSize));
                if (icn != null)
                {
                    return (icn.ToBitmap());
                }
                else
                {
                    return (GetNotFoundImage(pName, pSize));
                }
            }
            catch (MissingManifestResourceException)
            {
                try
                {
                    return (GetNotFoundImage(pName, pSize));
                }
                catch (Exception ex)
                {
                    throw new ApplicationException(" " + mClassName + "."
                        + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
                }
            }
            catch (Exception ex)
            {
                throw new ApplicationException(" " + mClassName + "."
                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
            }
        }

Per generare l’icona corretta, prendiamo l’icona origine leggendola dalle risorse e ne generiamo una della dimensione che ci serve. Essendo Icone sono quadrate. In caso di errore, chiamiamo la GetNotFoundImage precedentemente scritta.

Creiamo ora un metodo che rigenera una ImageList con icone alla massima definizione di colore a partire da una lista con i nomi delle icone da inserirvi.

public static void LoadIntoImageList(int pSize, ImageList pImageList, _
            ResourceManager pResx, List<string> pIconList)
        {
            pImageList.Images.Clear();
            pImageList.ImageSize = new Size(pSize, pSize);
            pImageList.ColorDepth = ColorDepth.Depth32Bit;

            for (int i = 0; i < pIconList.Count; i++)
            {
                Bitmap img = GetImageFromIcon(pSize, pIconList[i], pResx);
                pImageList.Images.Add(pIconList[i], img);
            }
        }

Questo metodo utilizza quanto scritto in precedenza, per cancellare e ripopolare una ImageList.

Ed infine il metodo più in alto, quello che useremo per costruire la nostra ImageList.

public static void BuildImageList(int pSize, ImageList pImgList, ResourceManager pResMgr)
        {
            List<string> keys = new List<string>();
            foreach (string key in pImgList.Images.Keys)
            {
                keys.Add(key);
            }
            LoadIntoImageList(pSize, pImgList, pResMgr, keys);

        }

Questo metodo, genera la lista dei nomi delle icone e poi le legge usando il metodo precedente.

NOTA:
Le immagini quando inserite nelle risorse del progetto, vengono incorporate escludendo dal nome la loro estensione.

Le immagini delle ImageList invece hanno anche l’estensione nel nome immagine. E’ quindi necessario fare una delle due cose qui indicate:

  • Cancellare l’estensione da tutti i nomi delle immagini nell’ImageList dopo averle aggiunte.
  • Modificare il metodo qui sopra per eliminare l’estensione dal nome immagine.

Entrambi sono semplici e validi, noi abbiamo scelto il primo per la nostra applicazione di test.

L’applicazione di test

In questa applicazione, utilizzeremo una form ed alcuni controlli per dimostrare come caricare a runtime una ToolStrip con dei tools modificandone le immagini rigenerandole usando le classi da noi preparate per permettere di modificarne la dimensione a piacere da 16 a 256 pixel.

Vediamo come è fatta la form.

sc_iconstoolstrip00

In questa form abbiamo inserito i seguenti controlli:

  • tStrip = ToolStrip Vuota
  • cboSizes = ComboBox che Lista le dimensioni possibili
  • btnChangeSize = Button per la rigenerazione delle icone
  • pcxImage = PictureBox per visualizzare le icone contenute nella imagelist alla dimensione esatta.
  • btnPrev = Button che visualizza l’immagine precedente nella lista, se arriva alla prima riparte dall’ultima.
  • btnNext = Button che visualizza l’immagine successiva nella lista, se arriva all’ultima riparte dalla prima.
  • imgButtons = ImageList in cui carichiamo le immagini che useremo per la demo.

Creiamo un file di risorse che chiameremo ResButtons.resx e infiliamovi dentro le stesse icone caricate nella ImageList.

sc_iconstoolstrip03

Vediamo ora il codice che abbiamo scritto:

private int mCurrentImage = 0;

Predisponiamo una variabile integer che sarà il nostro cursore di spostamento fra le immagini della imagelist quando useremo i bottoni btnPrev e btnNext.

public FrmMain()
        {
            InitializeComponent();
            this.cboSizes.Items.Add("16 x 16");
            this.cboSizes.Items.Add("24 x 24");
            this.cboSizes.Items.Add("32 x 32");
            this.cboSizes.Items.Add("48 x 48");
            this.cboSizes.Items.Add("64 x 64");
            this.cboSizes.Items.Add("96 x 96");
            this.cboSizes.Items.Add("128 x 128");
            this.cboSizes.Items.Add("256 x 256");
        }

nel costruttore, inizializziamo la combobox.

private void BuildToolbar()
        {
            try
            {
                tStrip.Items.Clear();
                foreach (string img in this.imgButtons.Images.Keys)
                {
                    ToolStripButton btn = new ToolStripButton();
                    btn.Name = img;
                    btn.Text = img;
                                  btn.Image = imgButtons.Images[img];
                    btn.Click += new EventHandler(btn_Click);
                    this.tStrip.Items.Add(btn);

                }
            }
            catch (Exception ex)
            {
                throw new ApplicationException(" " + mClassName + "."
                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
            }
        }

Creiamo un metodo che genera i bottoni della toolstrip partendo da ciò che contiene l’imagelist:

private void FrmMain_Load(object sender, EventArgs e)
        {
            this.cboSizes.SelectedIndex = 2;
            BuildToolbar();
        }

Al caricamento della form, generiamo la toolstrip e selezioniamo 32 x 32 dalla lista della combo.

private void btnChangeSize_Click(object sender, EventArgs e)
        {
            try
            {

                int[] values = new int[] { 16, 24, 32, 48, 64, 96, 128, 256 };
                BtnManager.BuildImageList(values[cboSizes.SelectedIndex], 
                   imgButtons, ResButtons.ResourceManager);
                tStrip.ImageScalingSize = new Size(values[cboSizes.SelectedIndex], 
                   values[cboSizes.SelectedIndex]);
                BuildToolbar();
                SetPicture();

            }
            catch (Exception ex)
            {
                throw new ApplicationException(" " + mClassName + "."
                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
            }
        }

Sul click del button di cambio dimensione, usiamo quanto predisposto nelle classi helper per rigenerare le icone, rigeneriamo la toolbar e carichiamo l’icona della imagelist selezionata anche sulla picturebox di controllo.

private void SetPicture()
        {
            try
            {
                pcxImage.Image = imgButtons.Images[mCurrentImage];
            }
            catch (Exception ex)
            {
                throw new ApplicationException(" " + mClassName + "."
                    + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
            }
        }

Il metodo che aggiorna la picturebox usando l’indice a livello di classe.

private void btnNext_Click(object sender, EventArgs e)
        {
            try
            {

                mCurrentImage++;
                if (mCurrentImage >= imgButtons.Images.Count) mCurrentImage = 0;
                SetPicture();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "ERRORE", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

Aggiungiamo il codice per spostare l’indice dell’immagine e aggiornare la picturebox anche al click dei bottoni. Sopra il tasto Successivo, sotto il tasto Precedente.

private void btnPrev_Click(object sender, EventArgs e)
        {
            try
            {
                mCurrentImage--;
                if (mCurrentImage < 0) mCurrentImage = imgButtons.Images.Count-1;
                SetPicture();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "ERRORE", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

Quel che succede a video se selezioniamo 16 x 16 pixel è questo:

sc_iconstoolstrip01

Selezionando invece 64 pixel otteniamo icone molto più visibili.

sc_iconstoolstrip02

Conclusioni

Abbiamo visto come in modo molto semplice, .NET ci permette di gestire le icone dei bottoni di una toolbar (o di qualsiasi altro controllo standard) e come rendere più usabile una applicazione cambiando in modo automatico la dimensione dei bottoni per l’utente.

Ovviamente questo è solo un piccolo esempio, però non è difficile modificarlo ed applicarlo ad una real world application. Basta essere un po’ accorti nella scelta dei nomi delle icone e lavorare un po’ sul codice, Non è necessario rigenerare i tools da codice, si può anche semplicemente sostituire le immagini in corsa.

Per inciso, se qualcuno si sta chiedendo per quale motivo passare per un imagelist invece che utilizzare direttamente le icone inserite nelle risorse, non c’è un motivo specifico se non che dal punto di vista pratico, le interfacce dei controlli che utilizzo solitamente per le toolbar, forniscono un supporto alle ImageList che rende molto più semplice scorrere le icone per assegnarle ai vari controlli inoltre l’ImageList contiene la lista dei nomi delle icone. Ma se preferite, potete anche costruire la vostra routine di sostituzione delle icone utilizzando direttamente il resource manager.

Il progetto di esempio può essere scaricato al link seguente:

Per qualsiasi domanda, curiosità e richiesta, potete usate pure il link al form di contatto in cima alla pagina.