source: trunk/eraser6/Eraser.Util/UxThemeApi.cs @ 1099

Revision 1099, 14.4 KB checked in by lowjoel, 5 years ago (diff)

Fixed all sizes to fit the Win7 (and probably Vista too) popup menu visual style.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.Runtime.InteropServices;
26using System.Windows.Forms;
27using System.Drawing;
28
29namespace Eraser.Util
30{
31    public static class UXThemeApi
32    {
33        /// <summary>
34        /// Updates the control's theme to fit in with the latest Windows visuals.
35        /// </summary>
36        /// <remarks>This function will also set the volume on all child controls.</remarks>
37        public static void UpdateControlTheme(Control control)
38        {
39            if (control is Form)
40                ((Form)control).Font = SystemFonts.MessageBoxFont;
41            if (control is ListView)
42                UpdateControlTheme((ListView)control);
43            else if (control is ToolStrip)
44                UpdateControlTheme((ToolStrip)control);
45
46            if (control.ContextMenuStrip != null)
47                UpdateControlTheme(control.ContextMenuStrip);
48           
49            foreach (Control child in control.Controls)
50                UpdateControlTheme(child);
51        }
52
53        /// <summary>
54        /// Updates the control's theme to fit in with the latest Windows visuals.
55        /// </summary>
56        /// <param name="lv">The List View control to set the theme on.</param>
57        public static void UpdateControlTheme(ListView lv)
58        {
59            try
60            {
61                NativeMethods.SetWindowTheme(lv.Handle, "EXPLORER", null);
62                UserApi.NativeMethods.SendMessage(lv.Handle,
63                    UserApi.NativeMethods.LVM_SETEXTENDEDLISTVIEWSTYLE,
64                    (UIntPtr)UserApi.NativeMethods.LVS_EX_DOUBLEBUFFER,
65                    (IntPtr)UserApi.NativeMethods.LVS_EX_DOUBLEBUFFER);
66            }
67            catch (DllNotFoundException)
68            {
69            }
70        }
71
72        /// <summary>
73        /// Updates the control's theme to fit in with the latest Windows visuals.
74        /// </summary>
75        /// <param name="menu">The tool strip control to set the theme on.</param>
76        public static void UpdateControlTheme(ToolStrip menu)
77        {
78            if (Environment.OSVersion.Version.Major >= 6)
79                if (menu.Renderer is ToolStripProfessionalRenderer)
80                    menu.Renderer = new UXThemeMenuRenderer();
81
82            foreach (ToolStripItem item in menu.Items)
83            {
84                ToolStripMenuItem toolStripItem = item as ToolStripMenuItem;
85                if (toolStripItem != null)
86                    UpdateControlTheme(toolStripItem);
87            }
88        }
89
90        /// <summary>
91        /// Updates the control's theme to fit in with the latest Windows visuals.
92        /// </summary>
93        /// <param name="menu">The List View control to set the theme on.</param>
94        public static void UpdateControlTheme(ToolStripDropDownItem menu)
95        {
96            UpdateControlTheme(menu.DropDown);
97        }
98
99        /// <summary>
100        /// Stores functions, structs and constants from UxTheme.dll and User32.dll
101        /// </summary>
102        internal static class NativeMethods
103        {
104            /// <summary>
105            /// Causes a window to use a different set of visual style information
106            /// than its class normally uses.
107            /// </summary>
108            /// <param name="hwnd">Handle to the window whose visual style information
109            /// is to be changed.</param>
110            /// <param name="pszSubAppName">Pointer to a string that contains the
111            /// application name to use in place of the calling application's name.
112            /// If this parameter is NULL, the calling application's name is used.</param>
113            /// <param name="pszSubIdList">Pointer to a string that contains a
114            /// semicolon-separated list of class identifier (CLSID) names to use
115            /// in place of the actual list passed by the window's class. If this
116            /// parameter is NULL, the ID list from the calling class is used.</param>
117            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
118            public static extern void SetWindowTheme(IntPtr hwnd, string pszSubAppName,
119                string pszSubIdList);
120        }
121    }
122
123    public class UXThemeMenuRenderer : ToolStripRenderer
124    {
125        ~UXThemeMenuRenderer()
126        {
127            hTheme.Close();
128        }
129
130        protected override void Initialize(ToolStrip toolStrip)
131        {
132            base.Initialize(toolStrip);
133
134            control = toolStrip;
135            hTheme = NativeMethods.OpenThemeData(toolStrip.Handle, "MENU");
136        }
137
138        protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
139        {
140            IntPtr hDC = e.Graphics.GetHdc();
141            Rectangle rect = e.AffectedBounds;
142
143            if (NativeMethods.IsThemeBackgroundPartiallyTransparent(hTheme,
144                (int)NativeMethods.MENUPARTS.MENU_POPUPITEM, 0))
145            {
146                NativeMethods.DrawThemeBackground(hTheme, hDC,
147                    (int)NativeMethods.MENUPARTS.MENU_POPUPBACKGROUND, 0, ref rect, ref rect);
148            }
149           
150            NativeMethods.DrawThemeBackground(hTheme, hDC, (int)
151                NativeMethods.MENUPARTS.MENU_POPUPBORDERS, 0, ref rect, ref rect);
152
153            e.Graphics.ReleaseHdc();
154            rect.Inflate(-Margin, -Margin);
155            e.Graphics.FillRectangle(new SolidBrush(e.BackColor), rect);
156        }
157
158        protected override void OnRenderImageMargin(ToolStripRenderEventArgs e)
159        {
160            IntPtr hDC = e.Graphics.GetHdc();
161            Rectangle rect = e.AffectedBounds;
162            rect.Inflate(-2, -2);
163            rect.Offset(1, 1);
164            rect.Size = new Size(GutterWidth, rect.Height + 1);
165
166            NativeMethods.DrawThemeBackground(hTheme, hDC,
167                (int)NativeMethods.MENUPARTS.MENU_POPUPGUTTER, 0, ref rect, ref rect);
168
169            e.Graphics.ReleaseHdc();
170        }
171
172        protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
173        {
174            Rectangle rect = Rectangle.Truncate(e.Graphics.VisibleClipBounds);
175            rect.Inflate(-1, 0);
176            rect.Offset(2, 0);
177            IntPtr hDC = e.Graphics.GetHdc();
178
179            int itemState = (int)(e.Item.Selected ?
180                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_HOT :
181                    NativeMethods.POPUPITEMSTATES.MPI_DISABLEDHOT) :
182                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_NORMAL :
183                    NativeMethods.POPUPITEMSTATES.MPI_DISABLED));
184            NativeMethods.DrawThemeBackground(hTheme, hDC,
185                (int)NativeMethods.MENUPARTS.MENU_POPUPITEM, itemState, ref rect, ref rect);
186
187            e.Graphics.ReleaseHdc();
188        }
189
190        protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
191        {
192            IntPtr hDC = e.Graphics.GetHdc();
193            Rectangle rect = new Rectangle(GutterWidth, 0, e.Item.Width, e.Item.Height);
194            rect.Inflate(4, 0);
195
196            NativeMethods.DrawThemeBackground(hTheme, hDC,
197                (int)NativeMethods.MENUPARTS.MENU_POPUPSEPARATOR, 0, ref rect, ref rect);
198
199            e.Graphics.ReleaseHdc();
200        }
201
202        protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
203        {
204            if (!(e.Item is ToolStripMenuItem))
205            {
206                base.OnRenderItemCheck(e);
207                return;
208            }
209
210            //Create the rectangle for the checkmark:
211            // 1. Offset by 2px
212            // 2. Inflate by (4, 3)
213            // 3. Increase width by 1px to fall on even x-coordinate (correction for odd pixel offset)
214            Rectangle imgRect = new Rectangle(e.ImageRectangle.Left + 2 - 4,
215                e.ImageRectangle.Top - 3,
216                e.ImageRectangle.Width + 4 * 2 + 1, e.ImageRectangle.Height + 3 * 2);
217
218            IntPtr hDC = e.Graphics.GetHdc();
219            ToolStripMenuItem item = (ToolStripMenuItem)e.Item;
220
221            int bgState = (int)(e.Item.Enabled ? NativeMethods.POPUPCHECKBACKGROUNDSTATES.MCB_NORMAL :
222                NativeMethods.POPUPCHECKBACKGROUNDSTATES.MCB_DISABLED);
223            NativeMethods.DrawThemeBackground(hTheme, hDC,
224                (int)NativeMethods.MENUPARTS.MENU_POPUPCHECKBACKGROUND, bgState,
225                ref imgRect, ref imgRect);
226
227            int checkState = (int)(item.Checked ?
228                (item.Enabled ? NativeMethods.POPUPCHECKSTATES.MC_CHECKMARKNORMAL :
229                    NativeMethods.POPUPCHECKSTATES.MC_CHECKMARKDISABLED) : 0);
230            NativeMethods.DrawThemeBackground(hTheme, hDC,
231                (int)NativeMethods.MENUPARTS.MENU_POPUPCHECK, checkState,
232                ref imgRect, ref imgRect);
233
234            e.Graphics.ReleaseHdc();
235        }
236
237        protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
238        {
239            int itemState = (int)(e.Item.Selected ?
240                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_HOT :
241                    NativeMethods.POPUPITEMSTATES.MPI_DISABLEDHOT) :
242                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_NORMAL :
243                    NativeMethods.POPUPITEMSTATES.MPI_DISABLED));
244
245            Rectangle newRect = e.TextRectangle;
246            newRect.Offset(2, 0);
247            e.TextRectangle = newRect;
248            Rectangle rect = new Rectangle(e.TextRectangle.Left, 0,
249                e.Item.Width - e.TextRectangle.Left, e.Item.Height);
250            IntPtr hFont = e.TextFont.ToHfont();
251            IntPtr hDC = e.Graphics.GetHdc();
252            NativeMethods.SelectObject(hDC, hFont);
253
254            NativeMethods.DrawThemeText(hTheme, hDC,
255                (int)NativeMethods.MENUPARTS.MENU_POPUPITEM, itemState, e.Text,
256                -1, e.TextFormat | TextFormatFlags.WordEllipsis | TextFormatFlags.SingleLine,
257                0, ref rect);
258
259            e.Graphics.ReleaseHdc();
260        }
261
262        protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
263        {
264            int itemState = (int)(e.Item.Enabled ? NativeMethods.POPUPSUBMENUSTATES.MSM_NORMAL :
265                NativeMethods.POPUPSUBMENUSTATES.MSM_DISABLED);
266
267            //Strangely, UxTheme won't draw any arrow once the starting coordinate
268            //is beyond 5px. So draw the arrow on a backing image then blit
269            //to the actual one.
270            using (Bitmap backBmp = new Bitmap(e.ArrowRectangle.Width, e.ArrowRectangle.Height))
271            {
272                using (Graphics backGfx = Graphics.FromImage(backBmp))
273                {
274                    IntPtr hDC = backGfx.GetHdc();
275
276                    Rectangle backRect = new Rectangle(new Point(0, 0), backBmp.Size);
277                    NativeMethods.DrawThemeBackground(hTheme, hDC,
278                        (int)NativeMethods.MENUPARTS.MENU_POPUPSUBMENU, itemState,
279                        ref backRect, ref backRect);
280                    backGfx.ReleaseHdc();
281                }
282
283                e.Graphics.DrawImageUnscaled(backBmp, e.ArrowRectangle);
284            }
285        }
286
287        private int GutterWidth
288        {
289            get
290            {
291                Rectangle margins = Rectangle.Empty;
292                Size checkSize = Size.Empty;
293
294                NativeMethods.GetThemeMargins(hTheme, IntPtr.Zero,
295                    (int)NativeMethods.MENUPARTS.MENU_POPUPCHECK, 0,
296                    (int)NativeMethods.TMT_MARGINS.TMT_SIZINGMARGINS,
297                    IntPtr.Zero, ref margins);
298                NativeMethods.GetThemePartSize(hTheme, IntPtr.Zero,
299                    (int)NativeMethods.MENUPARTS.MENU_POPUPCHECK, 0,
300                    IntPtr.Zero, NativeMethods.THEMESIZE.TS_TRUE, ref checkSize);
301                return 2 * checkSize.Width + margins.Left + margins.Width - 1;
302            }
303        }
304
305        private int Margin
306        {
307            get
308            {
309                Size borderSize = Size.Empty;
310                NativeMethods.GetThemePartSize(hTheme, IntPtr.Zero,
311                    (int)NativeMethods.MENUPARTS.MENU_POPUPBORDERS, 0,
312                    IntPtr.Zero, NativeMethods.THEMESIZE.TS_TRUE, ref borderSize);
313                return borderSize.Width;
314            }
315        }
316
317        private ToolStrip control;
318        private SafeThemeHandle hTheme;
319
320        /// <summary>
321        /// Imported UxTheme functions and constants.
322        /// </summary>
323        internal static class NativeMethods
324        {
325            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
326            public static extern SafeThemeHandle OpenThemeData(IntPtr hwnd, string pszClassList);
327
328            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
329            public static extern IntPtr CloseThemeData(IntPtr hwndTeme);
330
331            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
332            public static extern IntPtr DrawThemeParentBackground(IntPtr hwnd,
333                IntPtr hdc, ref Rectangle prc);
334
335            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
336            [return: MarshalAs(UnmanagedType.Bool)]
337            public static extern bool IsThemeBackgroundPartiallyTransparent(
338                SafeThemeHandle hTheme, int iPartId, int iStateId);
339
340            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
341            public static extern IntPtr DrawThemeBackground(
342                SafeThemeHandle hTheme, IntPtr hdc, int iPartId, int iStateId,
343                ref Rectangle pRect, ref Rectangle pClipRect);
344
345            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
346            public extern static int DrawThemeText(SafeThemeHandle hTheme,
347                IntPtr hDC, int iPartId, int iStateId,
348                [MarshalAs(UnmanagedType.LPWStr)] string pszText, int iCharCount,
349                TextFormatFlags dwTextFlag, int dwTextFlags2, ref Rectangle pRect);
350
351            [DllImport("Gdi32.dll")]
352            public extern static IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
353
354            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
355            public static extern IntPtr GetThemeMargins(SafeThemeHandle hTheme,
356                IntPtr hdc, int iPartId, int iStateId, int iPropId, ref Rectangle prc,
357                ref Rectangle pMargins);
358
359            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
360            public static extern IntPtr GetThemeMargins(SafeThemeHandle hTheme,
361                IntPtr hdc, int iPartId, int iStateId, int iPropId, IntPtr prc,
362                ref Rectangle pMargins);
363
364            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
365            public static extern IntPtr GetThemePartSize(SafeThemeHandle hTheme,
366                IntPtr hdc, int iPartId, int iStateId, ref Rectangle prc,
367                THEMESIZE eSize, ref Size psz);
368
369            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
370            public static extern IntPtr GetThemePartSize(SafeThemeHandle hTheme,
371                IntPtr hdc, int iPartId, int iStateId, IntPtr prc,
372                THEMESIZE eSize, ref Size psz);
373
374            public enum MENUPARTS
375            {
376                MENU_MENUITEM_TMSCHEMA = 1,
377                MENU_MENUDROPDOWN_TMSCHEMA = 2,
378                MENU_MENUBARITEM_TMSCHEMA = 3,
379                MENU_MENUBARDROPDOWN_TMSCHEMA = 4,
380                MENU_CHEVRON_TMSCHEMA = 5,
381                MENU_SEPARATOR_TMSCHEMA = 6,
382                MENU_BARBACKGROUND = 7,
383                MENU_BARITEM = 8,
384                MENU_POPUPBACKGROUND = 9,
385                MENU_POPUPBORDERS = 10,
386                MENU_POPUPCHECK = 11,
387                MENU_POPUPCHECKBACKGROUND = 12,
388                MENU_POPUPGUTTER = 13,
389                MENU_POPUPITEM = 14,
390                MENU_POPUPSEPARATOR = 15,
391                MENU_POPUPSUBMENU = 16,
392                MENU_SYSTEMCLOSE = 17,
393                MENU_SYSTEMMAXIMIZE = 18,
394                MENU_SYSTEMMINIMIZE = 19,
395                MENU_SYSTEMRESTORE = 20,
396            }
397
398            public enum POPUPCHECKSTATES
399            {
400                MC_CHECKMARKNORMAL = 1,
401                MC_CHECKMARKDISABLED = 2,
402                MC_BULLETNORMAL = 3,
403                MC_BULLETDISABLED = 4,
404            }
405
406            public enum POPUPCHECKBACKGROUNDSTATES
407            {
408                MCB_DISABLED = 1,
409                MCB_NORMAL = 2,
410                MCB_BITMAP = 3,
411            }
412
413            public enum POPUPITEMSTATES
414            {
415                MPI_NORMAL = 1,
416                MPI_HOT = 2,
417                MPI_DISABLED = 3,
418                MPI_DISABLEDHOT = 4,
419            }
420
421            public enum POPUPSUBMENUSTATES
422            {
423                MSM_NORMAL = 1,
424                MSM_DISABLED = 2,
425            }
426
427            public enum TMT_MARGINS
428            {
429                TMT_SIZINGMARGINS = 3601,
430                TMT_CONTENTMARGINS,
431                TMT_CAPTIONMARGINS
432            }
433
434            public enum THEMESIZE
435            {
436                TS_MIN,
437                TS_TRUE,
438                TS_DRAW
439            }
440        }
441    }
442
443    internal class SafeThemeHandle : SafeHandle
444    {
445        public SafeThemeHandle()
446            : base(IntPtr.Zero, true)
447        {
448        }
449
450        public override bool IsInvalid
451        {
452            get { return handle == IntPtr.Zero; }
453        }
454
455        protected override bool ReleaseHandle()
456        {
457            UXThemeMenuRenderer.NativeMethods.CloseThemeData(handle);
458            handle = IntPtr.Zero;
459            return true;
460        }
461    }
462}
Note: See TracBrowser for help on using the repository browser.