Prosty kafelkowy pulpit

Wstęp

Artykuł pokazuje, jak stworzyć prosty kafelkowy pulpit na Windows 7 i Windows XP.

W chwili publikacji na rynku funkcjonują już systemy Windows 8, Windows 8.1, a wkrótce ma wejść do sprzedaży wersja Windows 10.

Wszystkie one posiadają kafelkowe pulpity, przy czym pulpity te są znacznie bardziej rozbudowane, niż pokazany w artykule.

Sylistyka kafelków jednym przypada do gustu, innym nie. Autor jest w tej pierwszej grupie, niemniej ze względów zawodowych porusza się wciąż po systemach starszych.

Ale i w tych systemach pokazany pulpit może znaleźć zastosowanie, jako element urozmaicający interfejs użytkownika.

Można tak pokazywać niekoniecznie pulpit, ale wybrane foldery związane np. z tworzonymi aplikacjami.

Kontrolka kafelek (Tile) nie została wykorzystana wprost, ale za pośrednictwem panelu TileDesktopGroup, który umieszcza się na formie podczas projektowania.

Na końcu artykułu autor zamieścił projekt do pobrania.

Kod kontrolki kafelek (Tile)

using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;

namespace MetroEx
{
    // kontrolka kafelek
    public partial class Tile : UserControl
    {
        // kolory kafelków
        private static Color[] BackColorPalette =
        {
            Color.Goldenrod,
            Color.DarkSlateBlue,
            Color.LimeGreen,
            Color.OrangeRed,
            Color.Blue,
            Color.Teal
        };

        private string fileName; // nazwa pliku lub skrótu
        
        // obliczenie ilości kolorów w palecie
        private readonly int paletteBackColorCount = BackColorPalette.Count<Color>();
        
        private readonly int gap = 8; // odstęp między kafelkami
        private Font font = new Font("Tahoma", 8, FontStyle.Bold); // czcionka label1

        // dla XP ikony będą 32x32 ze względu na dużą ilość starszych programów które nie miały ikon 48x48
        private bool IsWinXP()
        {
            OperatingSystem OS = Environment.OSVersion;
            return (OS.Platform == PlatformID.Win32NT) && ((OS.Version.Major == 5) && (OS.Version.Minor > 0));
        }

        // konstruktor kafelka - rozmieszczanie i nadawanie koloru
        public Tile(int left, int top, int colorIndex)
        {
            InitializeComponent();

            //
            // Tile
            //
            this.Size = new Size(128, 128);
            this.Location = new Point(left * (this.Width + gap) + gap, top * (this.Height + gap) + gap);
            this.BackColor = BackColorPalette[colorIndex % paletteBackColorCount];
            this.DoubleBuffered = true;

            // podwójne kliknięcie w obszarze kafelka poza pictureBox1 i label1
            // funkcja obsługi identyczna dla wszystkich składników komponentu
            this.DoubleClick += new EventHandler(Tile_DoubleClick);
            
            //
            // pictureBox1
            //
            if (IsWinXP())
            {
                pictureBox1.Size = new Size(32, 32);
            }
            else
            {
                pictureBox1.Size = new Size(48, 48);
            }

            pictureBox1.Location = new Point(4, 4);
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;

            // podwójne kliknięcie w obszarze pictureBox1
            pictureBox1.DoubleClick += new EventHandler(Tile_DoubleClick);
            
            //
            // label1
            //
            label1.AutoSize = false;
            label1.Location = new Point(4, pictureBox1.Bottom);
            label1.Size = new Size(Width - 8, Height - pictureBox1.Height - 8);
            label1.Font = font;
            label1.TextAlign = ContentAlignment.BottomLeft;
            label1.ForeColor = Color.White;

            // podwójne kliknięcie w obszarze label1
            label1.DoubleClick += new EventHandler(Tile_DoubleClick);
        }

        // obsługa podwójnego kliknięcia na kafelku; o rodzaju obsługi decyduje system w oparciu o rozszerzenie nazwy pliku
        private void Tile_DoubleClick(object sender, EventArgs e)
        {
            try
            {
                Process Run = new Process();
                Run.StartInfo.FileName = fileName;
                Run.Start();
            }
            catch 
            { 
                // uruchomienie programu czasem poprzedza dialog z pytaniem czy zezwolić na uruchomienie
                // odmowa użytkownia prowadzi do wyjątku który musi zostać tu obsłużony; wystarczy catch { }
            }
        }

        // własność nazwa pliku (skrót do pliku docelowego lub plik docelowy)
        public string FileName
        {
            set
            {
                fileName = value; // podstawienie wartości nazwa pliku

                // wyłączenie wyświetlania wszystkich rozszerzeń nazw
                label1.Text = Path.GetFileNameWithoutExtension(value); // nazwa wyświetlana na kafelku w label1

                // rozwiązanie alternatywne - programista dokonuje wyboru ukrywanych rozszerzeń
                /*
                label1.Text = label1.Text.Replace(".dpr", "");
                label1.Text = label1.Text.Replace(".csproj", "");
                label1.Text = label1.Text.Replace(".exe", "");
                label1.Text = label1.Text.Replace(".lnk", "");
                 */
                
                // pominięcie tekstu "Skrót do " w Windows XP
                label1.Text = label1.Text.Replace("Skrót do ", "");

                FileInfo fi = new FileInfo(value);

                string ext = fi.Extension.ToLower();

                if (ext == ".png" || ext.EndsWith(".jpg") || ext == ".bmp")
                {
                    // szczególne zachowanie w celu wyświetlenia podglądu obrazka - zmienia się położenie i rozmiar pictureBox1 i label1
                    // autor pominął pliki których proporcje są typu portret (portrait)

                    int labelHeight = TextRenderer.MeasureText(label1.Text, font).Height;
                    pictureBox1.Location = new Point(0, 0);
                    pictureBox1.Size = new Size(this.Width, this.Height - 3 * labelHeight - 2);
                    Bitmap b = new Bitmap(value);
                    pictureBox1.Image = b;
                    label1.Location = new Point(0, pictureBox1.Bottom - 2);
                    label1.Size = new Size(this.Width, 3 * labelHeight);
                }
                else 
                {
                    pictureBox1.Image = ShellEx.GetBitmapFromPath(value);
                }
            }
            get // pobranie wartości nazwa pliku
            {
                return fileName;
            }
        }
    }
}

Kod kontrolki grupa kafelków (TileDesktopGroup)

using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;

namespace MetroEx
{
    // kontrolka grupa kafelków pulpitu
    class TileDesktopGroup : Panel
    {
        // stałe związane z rozmieszczniem kafelków (Tile)
        // w szczególności vertDivider określa ilość kafelków w pionie
        // istnieje zależność między paletą kolorów a doborem tych stałych
        private const int vertDivider = 4;
        private const int horzDivider = 4;

        // tablice nazw plików
        private string[] userFiles;
        private string[] publicFiles;

        // ścieżka do pulpitu użykownika
        string userDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

        // ścieżka do pulpitu publicznego (wspólnego wszystkich użykowników)
        // część skrótów pomimo że jest wyświetlana na pulpicie użytkownika
        // jest pobierana z katalogu pulpit publiczny
        // i nie istnieje w katalogu pulpitu danego użytkownika
        string publicDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
     
        // przygotowanie grupy kafelków
        private void Prepare()
        {
            // użyte w celu zmniejszenia efektu migotania
            this.DoubleBuffered = true;

            // przejrzystość koloru między kafelkami
            this.BackColor = Color.Transparent; 
                                   
            // prosty sposób na zamianę panelu c# na odpowiednik ScrollBox delphi
            // dzięki temu zawartość można przesuwać suwakami (scrollBar)
            this.AutoScroll = true;
        }

        // pobranie nazw plików do tablic
        private void GetFiles()
        {
            userFiles = Directory.GetFileSystemEntries(userDesktopPath);
            publicFiles = Directory.GetFileSystemEntries(publicDesktopPath);
        }

        // filtr wykluczający część plików
        // w szczególności dotyczy Windows 7 i komputera autora (.picasaoriginals)
        private bool Filter(string name, string ext)
        {
            return !(ext.EndsWith(".ini") || ext.EndsWith(".tmp") || ext.EndsWith(".picasaoriginals") || name.StartsWith("~"));
        }

        // utworzenie i wyświetlenie grupy kafelków
        public void ShowDesktop()
        {
            int i = 0;
            int color = 0;

            Prepare();
            GetFiles();

            foreach (string s in userFiles)
            {
                FileInfo fi = new FileInfo(s);

                if (Filter(fi.Name, fi.Extension.ToLower()))
                {
                    // utworzenie nowego obiektu kafelek (Tile)
                    // nadanie mu położenia i koloru
                    Tile t = new Tile(i / horzDivider, i % vertDivider, color);

                    // nadanie tytułu kafelka i i dodanie do zbioru kontrolek TileDesktopGroup
                    t.FileName = s;
                    this.Controls.Add(t);
                    color++;
                    i++;
                }
            }

            // analogicznie jak opis poprzedniej pętli foreach
            foreach (string s in publicFiles)
            {
                FileInfo fi = new FileInfo(s);

                if (Filter(fi.Name, fi.Extension.ToLower()))
                {
                    Tile t = new Tile(i / horzDivider, i % vertDivider, color);

                    t.FileName = s;
                    this.Controls.Add(t);
                    color++;
                    i++;
                }
            }
        }
    }
}

Kod klasy ShellEx

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Drawing;

namespace MetroEx
{
    /// <summary>
    /// Źródło
    /// http://www.vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/SysImageList/article.asp
    /// http://lluisfranco.com/2014/04/16/extract-extra-large-icon-from-a-file-including-network-paths/
    /// </summary>

    class ShellEx
    {
        [DllImport("shell32.dll")]
        private static extern int SHGetImageList(int iImageList, ref Guid riid, out IImageList ppv);

        [DllImport("shell32.dll")]
        public static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, uint uFlags);

        [DllImport("user32")]
        public static extern int DestroyIcon(IntPtr hIcon);

        // struktura  SHFILEINFO
        [StructLayout(LayoutKind.Sequential)]
        public struct SHFILEINFO
        {
            public IntPtr hIcon;
            public int iIcon;
            public uint dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
        };

        // niezbędne flagi
        public const int  SHIL_EXTRALARGE = 0x2;
        public const int  SHIL_JUMBO = 0x4;
        public const uint SHGFI_SYSICONINDEX = 0x4000;
        public const uint SHGFI_ICON = 0x100;
        public const uint SHGFI_USEFILEATTRIBUTES = 0x10;
        public const int  FILE_ATTRIBUTE_NORMAL = 0x80;
        public const int  FILE_ATTRIBUTE_DIRECTORY = 0x10; 

        // struktura do interfejsu COM IImageList
        public struct IMAGELISTDRAWPARAMS
        {
            public int cbSize;
            public IntPtr himl;
            public int i;
            public IntPtr hdcDst;
            public int x;
            public int y;
            public int cx;
            public int cy;
            public int xBitmap;
            public int yBitmap;
            public int rgbBk;
            public int rgbFg;
            public int fStyle;
            public int dwRop;
            public int fState;
            public int Frame;
            public int crEffect;
        }

        // interfejs COM IImageList
        [ComImportAttribute()]
        [GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950")]
        [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IImageList
        {
            [PreserveSig]
            int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi);

            [PreserveSig]
            int ReplaceIcon(int i, IntPtr hicon, ref int pi);

            [PreserveSig]
            int SetOverlayImage(int iImage, int iOverlay);

            [PreserveSig]
            int Replace(int i, IntPtr hbmImage, IntPtr hbmMask);

            [PreserveSig]
            int AddMasked(IntPtr hbmImage, int crMask, ref int pi);

            [PreserveSig]
            int Draw(ref IMAGELISTDRAWPARAMS pimldp);

            [PreserveSig]
            int Remove(int i);

            [PreserveSig]
            int GetIcon(int i, int flags, ref IntPtr picon);
        };

        // rozmiary ikon
        public enum IconSizeEnum
        {
            LargeIcon48 = SHIL_EXTRALARGE, // 48x48
            ExtraLargeIcon = SHIL_JUMBO    // 256x256
        }

        // pobranie bitmapy z ikony folderu lub pliku
        public static Bitmap GetBitmapFromPath(string filepath)
        {
            IntPtr hIcon = IntPtr.Zero;
            var shinfo = new SHFILEINFO();

            if (Directory.Exists(filepath))
            {
                hIcon = getIconHandle(filepath, IconSizeEnum.LargeIcon48, ref shinfo, FILE_ATTRIBUTE_DIRECTORY, SHGFI_ICON | SHGFI_USEFILEATTRIBUTES);
            }
            else
            {
                if (File.Exists(filepath))
                {
                    hIcon = getIconHandle(filepath, IconSizeEnum.LargeIcon48, ref shinfo, FILE_ATTRIBUTE_NORMAL, SHGFI_SYSICONINDEX);
                }
            }
            return getBitmapFromIconHandle(hIcon);
        }

        // pobranie bitmapy w oparciu o uchwyt ikony (bez tła ikony)
        private static Bitmap getBitmapFromIconHandle(IntPtr hIcon)
        {
            if (hIcon == IntPtr.Zero) throw new FileNotFoundException();
            var myIcon = Icon.FromHandle(hIcon);
            var bitmap = myIcon.ToBitmap();
            myIcon.Dispose();
            DestroyIcon(hIcon);
            return bitmap;
        }

        // pobranie uchwytu ikony w oparciu o ścieżkę pliku lub folderu
        private static IntPtr getIconHandle(string filepath, IconSizeEnum iconsize, ref SHFILEINFO shinfo, int fileAttributeFlag, uint flags)
        {
            const int ILD_TRANSPARENT = 1; 
            var retval = SHGetFileInfo(filepath, fileAttributeFlag, ref shinfo, Marshal.SizeOf(shinfo), flags);
            if (retval == 0) throw new FileNotFoundException();
            var iconIndex = shinfo.iIcon;
            var iImageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
            IImageList iml;
            var hres = SHGetImageList((int)iconsize, ref iImageListGuid, out iml);
            var hIcon = IntPtr.Zero;
            hres = iml.GetIcon(iconIndex, ILD_TRANSPARENT, ref hIcon); 
            return hIcon;
        }
    }
}

Przykładowe użycie

using System;
using System.Windows.Forms;

namespace Metro
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            
            //
            // Form1
            //
            this.Load += new EventHandler(Form1_Load);
        }

        void Form1_Load(object sender, EventArgs e)
        {
            tileDesktopGroup1.ShowDesktop();
            //tileDesktopGroup2.ShowDesktop();
            //...
        }
    }
}

Zrzuty ekranu z Windows 7 i Windows XP

win7.png

Kafelki na Windows 7

win7_dwie_grupy.png

Kafelki na Windows w dwóch grupach

xp.png

Kafelki na Windows XP

Projekt autora do pobrania

MetroEx.zip

0 komentarzy