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

Revision 1105, 17.8 KB checked in by lowjoel, 5 years ago (diff)

Handle theme changes so when Comctl32.dll v6 is unloaded we revert to .NET rendering (and through the DWM APIs we restore UxTheme? rendering when UxTheme? is reloaded - may not always work but this should suffice)

  • 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;
28using System.IO;
29
30namespace Eraser.Util
31{
32    public static class UXThemeApi
33    {
34        /// <summary>
35        /// Updates the control's theme to fit in with the latest Windows visuals.
36        /// </summary>
37        /// <remarks>This function will also set the volume on all child controls.</remarks>
38        public static void UpdateControlTheme(Control control)
39        {
40            if (control is Form)
41                ((Form)control).Font = SystemFonts.MessageBoxFont;
42            if (control is ListView)
43                UpdateControlTheme((ListView)control);
44            else if (control is ToolStrip)
45                UpdateControlTheme((ToolStrip)control);
46
47            if (control.ContextMenuStrip != null)
48                UpdateControlTheme(control.ContextMenuStrip);
49           
50            foreach (Control child in control.Controls)
51                UpdateControlTheme(child);
52        }
53
54        /// <summary>
55        /// Updates the control's theme to fit in with the latest Windows visuals.
56        /// </summary>
57        /// <param name="lv">The List View control to set the theme on.</param>
58        public static void UpdateControlTheme(ListView lv)
59        {
60            try
61            {
62                NativeMethods.SetWindowTheme(lv.Handle, "EXPLORER", null);
63                UserApi.NativeMethods.SendMessage(lv.Handle,
64                    UserApi.NativeMethods.LVM_SETEXTENDEDLISTVIEWSTYLE,
65                    (UIntPtr)UserApi.NativeMethods.LVS_EX_DOUBLEBUFFER,
66                    (IntPtr)UserApi.NativeMethods.LVS_EX_DOUBLEBUFFER);
67            }
68            catch (DllNotFoundException)
69            {
70            }
71        }
72
73        /// <summary>
74        /// Updates the control's theme to fit in with the latest Windows visuals.
75        /// </summary>
76        /// <param name="menu">The tool strip control to set the theme on.</param>
77        public static void UpdateControlTheme(ToolStrip menu)
78        {
79            //Register for Theme changed messages
80            if (ThemeMessageFilter.Instance == null)
81            {
82                ThemeMessageFilter filter = new ThemeMessageFilter();
83                ThemeMessageFilter.Instance.ThemeChanged += OnThemeChanged;
84            }
85
86            if (Environment.OSVersion.Version.Major >= 6)
87            {
88                //Assign our themed renderer for non-custom renderers
89                UXThemeMenuRenderer renderer = new UXThemeMenuRenderer();
90                if (menu.Renderer is ToolStripProfessionalRenderer)
91                {
92                    menu.Disposed += OnThemedMenuDisposed;
93                    ThemedMenus.Add(menu, renderer);
94                    if (NativeMethods.ThemesActive)
95                        menu.Renderer = renderer;
96                }
97            }
98
99            foreach (ToolStripItem item in menu.Items)
100            {
101                ToolStripMenuItem toolStripItem = item as ToolStripMenuItem;
102                if (toolStripItem != null)
103                    UpdateControlTheme(toolStripItem);
104            }
105        }
106
107        /// <summary>
108        /// Updates the control's theme to fit in with the latest Windows visuals.
109        /// </summary>
110        /// <param name="menu">The List View control to set the theme on.</param>
111        public static void UpdateControlTheme(ToolStripDropDownItem menu)
112        {
113            UpdateControlTheme(menu.DropDown);
114        }
115
116        /// <summary>
117        /// Handles the theme changed event - reassigning the renderers to managed
118        /// context menus.
119        /// </summary>
120        private static void OnThemeChanged(object sender, EventArgs e)
121        {
122            bool themesActive = NativeMethods.ThemesActive;
123            foreach (KeyValuePair<ToolStrip, UXThemeMenuRenderer> value in ThemedMenus)
124            {
125                if (themesActive)
126                    value.Key.Renderer = value.Value;
127                else
128                    value.Key.RenderMode = ToolStripRenderMode.ManagerRenderMode;
129            }
130        }
131
132        /// <summary>
133        /// Clean up the reference to the menu when the menu is disposed so we no
134        /// longer track the menu for theme changes.
135        /// </summary>
136        private static void OnThemedMenuDisposed(object sender, EventArgs e)
137        {
138            ThemedMenus.Remove(sender as ToolStrip);
139        }
140
141        /// <summary>
142        /// The private list of menus which has their render changed to the UxTheme renderer.
143        /// This allows us to revert the renderer back to the default Professional
144        /// renderer when we get a theme changed message.
145        /// </summary>
146        private static Dictionary<ToolStrip, UXThemeMenuRenderer> ThemedMenus =
147            new Dictionary<ToolStrip,UXThemeMenuRenderer>();
148
149        /// <summary>
150        /// Filters the Application message loop for WM_THEMECHANGED messages
151        /// and broadcasts them to the event handlers.
152        /// </summary>
153        private class ThemeMessageFilter : IMessageFilter
154        {
155            public ThemeMessageFilter()
156            {
157                if (Instance != null)
158                    throw new InvalidOperationException("Only one instance of the " +
159                        "ThemeMessageFilter can exist at any one time,");
160                Instance = this;
161                ThemesActive = NativeMethods.ThemesActive;
162                Application.AddMessageFilter(this);
163            }
164
165            #region IMessageFilter Members
166            public bool PreFilterMessage(ref Message m)
167            {
168                if (m.Msg == WM_THEMECHANGED)
169                {
170                    ThemesActive = NativeMethods.ThemesActive;
171                    ThemeChanged(null, EventArgs.Empty);
172                }
173                else if (m.Msg == WM_DWMCOMPOSITIONCHANGED)
174                {
175                    if (ThemesActive != NativeMethods.ThemesActive)
176                    {
177                        ThemesActive = NativeMethods.ThemesActive;
178                        ThemeChanged(null, EventArgs.Empty);
179                    }
180                }
181
182                return false;           
183            }
184            #endregion
185
186            /// <summary>
187            /// The global ThemeMessageFilter instance.
188            /// </summary>
189            public static ThemeMessageFilter Instance
190            {
191                get;
192                private set;
193            }
194
195            /// <summary>
196            /// Called when a WM_THEMECHANGED message is sent.
197            /// </summary>
198            public EventHandler<EventArgs> ThemeChanged
199            {
200                get;
201                set;
202            }
203
204            private const int WM_THEMECHANGED = 0x031A;
205            private const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
206            private bool ThemesActive;
207        }
208
209        /// <summary>
210        /// Stores functions, structs and constants from UxTheme.dll and User32.dll
211        /// </summary>
212        internal static class NativeMethods
213        {
214            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
215            [return: MarshalAs(UnmanagedType.Bool)]
216            private static extern bool IsThemeActive();
217
218            public static bool ThemesActive
219            {
220                get
221                {
222                    try
223                    {
224                        return IsThemeActive();
225                    }
226                    catch (FileLoadException)
227                    {
228                        return false;
229                    }
230                }
231            }
232
233            /// <summary>
234            /// Causes a window to use a different set of visual style information
235            /// than its class normally uses.
236            /// </summary>
237            /// <param name="hwnd">Handle to the window whose visual style information
238            /// is to be changed.</param>
239            /// <param name="pszSubAppName">Pointer to a string that contains the
240            /// application name to use in place of the calling application's name.
241            /// If this parameter is NULL, the calling application's name is used.</param>
242            /// <param name="pszSubIdList">Pointer to a string that contains a
243            /// semicolon-separated list of class identifier (CLSID) names to use
244            /// in place of the actual list passed by the window's class. If this
245            /// parameter is NULL, the ID list from the calling class is used.</param>
246            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
247            public static extern void SetWindowTheme(IntPtr hwnd, string pszSubAppName,
248                string pszSubIdList);
249        }
250    }
251
252    public class UXThemeMenuRenderer : ToolStripRenderer
253    {
254        ~UXThemeMenuRenderer()
255        {
256            if (hTheme != null)
257                hTheme.Close();
258        }
259
260        protected override void Initialize(ToolStrip toolStrip)
261        {
262            base.Initialize(toolStrip);
263
264            control = toolStrip;
265            hTheme = NativeMethods.OpenThemeData(toolStrip.Handle, "MENU");
266        }
267
268        protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
269        {
270            IntPtr hDC = e.Graphics.GetHdc();
271            Rectangle rect = e.AffectedBounds;
272
273            if (NativeMethods.IsThemeBackgroundPartiallyTransparent(hTheme,
274                (int)NativeMethods.MENUPARTS.MENU_POPUPITEM, 0))
275            {
276                NativeMethods.DrawThemeBackground(hTheme, hDC,
277                    (int)NativeMethods.MENUPARTS.MENU_POPUPBACKGROUND, 0, ref rect, ref rect);
278            }
279           
280            NativeMethods.DrawThemeBackground(hTheme, hDC, (int)
281                NativeMethods.MENUPARTS.MENU_POPUPBORDERS, 0, ref rect, ref rect);
282
283            e.Graphics.ReleaseHdc();
284            rect.Inflate(-Margin, -Margin);
285            e.Graphics.FillRectangle(new SolidBrush(e.BackColor), rect);
286        }
287
288        protected override void OnRenderImageMargin(ToolStripRenderEventArgs e)
289        {
290            IntPtr hDC = e.Graphics.GetHdc();
291            Rectangle rect = e.AffectedBounds;
292            rect.Inflate(-2, -2);
293            rect.Offset(1, 1);
294            rect.Size = new Size(GutterWidth, rect.Height + 1);
295
296            NativeMethods.DrawThemeBackground(hTheme, hDC,
297                (int)NativeMethods.MENUPARTS.MENU_POPUPGUTTER, 0, ref rect, ref rect);
298
299            e.Graphics.ReleaseHdc();
300        }
301
302        protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
303        {
304            Rectangle rect = Rectangle.Truncate(e.Graphics.VisibleClipBounds);
305            rect.Inflate(-1, 0);
306            rect.Offset(2, 0);
307            IntPtr hDC = e.Graphics.GetHdc();
308
309            int itemState = (int)(e.Item.Selected ?
310                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_HOT :
311                    NativeMethods.POPUPITEMSTATES.MPI_DISABLEDHOT) :
312                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_NORMAL :
313                    NativeMethods.POPUPITEMSTATES.MPI_DISABLED));
314            NativeMethods.DrawThemeBackground(hTheme, hDC,
315                (int)NativeMethods.MENUPARTS.MENU_POPUPITEM, itemState, ref rect, ref rect);
316
317            e.Graphics.ReleaseHdc();
318        }
319
320        protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
321        {
322            IntPtr hDC = e.Graphics.GetHdc();
323            Rectangle rect = new Rectangle(GutterWidth, 0, e.Item.Width, e.Item.Height);
324            rect.Inflate(4, 0);
325
326            NativeMethods.DrawThemeBackground(hTheme, hDC,
327                (int)NativeMethods.MENUPARTS.MENU_POPUPSEPARATOR, 0, ref rect, ref rect);
328
329            e.Graphics.ReleaseHdc();
330        }
331
332        protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
333        {
334            if (!(e.Item is ToolStripMenuItem))
335            {
336                base.OnRenderItemCheck(e);
337                return;
338            }
339
340            //Create the rectangle for the checkmark:
341            // 1. Offset by 2px
342            // 2. Inflate by (4, 3)
343            // 3. Increase width by 1px to fall on even x-coordinate (correction for odd pixel offset)
344            Rectangle imgRect = new Rectangle(e.ImageRectangle.Left + 2 - 4,
345                e.ImageRectangle.Top - 3,
346                e.ImageRectangle.Width + 4 * 2 + 1, e.ImageRectangle.Height + 3 * 2);
347
348            IntPtr hDC = e.Graphics.GetHdc();
349            ToolStripMenuItem item = (ToolStripMenuItem)e.Item;
350
351            int bgState = (int)(e.Item.Enabled ? NativeMethods.POPUPCHECKBACKGROUNDSTATES.MCB_NORMAL :
352                NativeMethods.POPUPCHECKBACKGROUNDSTATES.MCB_DISABLED);
353            NativeMethods.DrawThemeBackground(hTheme, hDC,
354                (int)NativeMethods.MENUPARTS.MENU_POPUPCHECKBACKGROUND, bgState,
355                ref imgRect, ref imgRect);
356
357            int checkState = (int)(item.Checked ?
358                (item.Enabled ? NativeMethods.POPUPCHECKSTATES.MC_CHECKMARKNORMAL :
359                    NativeMethods.POPUPCHECKSTATES.MC_CHECKMARKDISABLED) : 0);
360            NativeMethods.DrawThemeBackground(hTheme, hDC,
361                (int)NativeMethods.MENUPARTS.MENU_POPUPCHECK, checkState,
362                ref imgRect, ref imgRect);
363
364            e.Graphics.ReleaseHdc();
365        }
366
367        protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
368        {
369            int itemState = (int)(e.Item.Selected ?
370                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_HOT :
371                    NativeMethods.POPUPITEMSTATES.MPI_DISABLEDHOT) :
372                (e.Item.Enabled ? NativeMethods.POPUPITEMSTATES.MPI_NORMAL :
373                    NativeMethods.POPUPITEMSTATES.MPI_DISABLED));
374
375            Rectangle newRect = e.TextRectangle;
376            newRect.Offset(2, 0);
377            e.TextRectangle = newRect;
378            Rectangle rect = new Rectangle(e.TextRectangle.Left, 0,
379                e.Item.Width - e.TextRectangle.Left, e.Item.Height);
380            IntPtr hFont = e.TextFont.ToHfont();
381            IntPtr hDC = e.Graphics.GetHdc();
382            NativeMethods.SelectObject(hDC, hFont);
383
384            NativeMethods.DrawThemeText(hTheme, hDC,
385                (int)NativeMethods.MENUPARTS.MENU_POPUPITEM, itemState, e.Text,
386                -1, e.TextFormat | TextFormatFlags.WordEllipsis | TextFormatFlags.SingleLine,
387                0, ref rect);
388
389            e.Graphics.ReleaseHdc();
390        }
391
392        protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
393        {
394            int itemState = (int)(e.Item.Enabled ? NativeMethods.POPUPSUBMENUSTATES.MSM_NORMAL :
395                NativeMethods.POPUPSUBMENUSTATES.MSM_DISABLED);
396
397            //Strangely, UxTheme won't draw any arrow once the starting coordinate
398            //is beyond 5px. So draw the arrow on a backing image then blit
399            //to the actual one.
400            using (Bitmap backBmp = new Bitmap(e.ArrowRectangle.Width, e.ArrowRectangle.Height))
401            {
402                using (Graphics backGfx = Graphics.FromImage(backBmp))
403                {
404                    IntPtr hDC = backGfx.GetHdc();
405
406                    Rectangle backRect = new Rectangle(new Point(0, 0), backBmp.Size);
407                    NativeMethods.DrawThemeBackground(hTheme, hDC,
408                        (int)NativeMethods.MENUPARTS.MENU_POPUPSUBMENU, itemState,
409                        ref backRect, ref backRect);
410                    backGfx.ReleaseHdc();
411                }
412
413                e.Graphics.DrawImageUnscaled(backBmp, e.ArrowRectangle);
414            }
415        }
416
417        private int GutterWidth
418        {
419            get
420            {
421                Rectangle margins = Rectangle.Empty;
422                Size checkSize = Size.Empty;
423
424                NativeMethods.GetThemeMargins(hTheme, IntPtr.Zero,
425                    (int)NativeMethods.MENUPARTS.MENU_POPUPCHECK, 0,
426                    (int)NativeMethods.TMT_MARGINS.TMT_SIZINGMARGINS,
427                    IntPtr.Zero, ref margins);
428                NativeMethods.GetThemePartSize(hTheme, IntPtr.Zero,
429                    (int)NativeMethods.MENUPARTS.MENU_POPUPCHECK, 0,
430                    IntPtr.Zero, NativeMethods.THEMESIZE.TS_TRUE, ref checkSize);
431                return 2 * checkSize.Width + margins.Left + margins.Width - 1;
432            }
433        }
434
435        private int Margin
436        {
437            get
438            {
439                Size borderSize = Size.Empty;
440                NativeMethods.GetThemePartSize(hTheme, IntPtr.Zero,
441                    (int)NativeMethods.MENUPARTS.MENU_POPUPBORDERS, 0,
442                    IntPtr.Zero, NativeMethods.THEMESIZE.TS_TRUE, ref borderSize);
443                return borderSize.Width;
444            }
445        }
446
447        private ToolStrip control;
448        private SafeThemeHandle hTheme;
449
450        /// <summary>
451        /// Imported UxTheme functions and constants.
452        /// </summary>
453        internal static class NativeMethods
454        {
455            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
456            public static extern SafeThemeHandle OpenThemeData(IntPtr hwnd, string pszClassList);
457
458            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
459            public static extern IntPtr CloseThemeData(IntPtr hwndTeme);
460
461            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
462            public static extern IntPtr DrawThemeParentBackground(IntPtr hwnd,
463                IntPtr hdc, ref Rectangle prc);
464
465            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
466            [return: MarshalAs(UnmanagedType.Bool)]
467            public static extern bool IsThemeBackgroundPartiallyTransparent(
468                SafeThemeHandle hTheme, int iPartId, int iStateId);
469
470            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
471            public static extern IntPtr DrawThemeBackground(
472                SafeThemeHandle hTheme, IntPtr hdc, int iPartId, int iStateId,
473                ref Rectangle pRect, ref Rectangle pClipRect);
474
475            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
476            public extern static int DrawThemeText(SafeThemeHandle hTheme,
477                IntPtr hDC, int iPartId, int iStateId,
478                [MarshalAs(UnmanagedType.LPWStr)] string pszText, int iCharCount,
479                TextFormatFlags dwTextFlag, int dwTextFlags2, ref Rectangle pRect);
480
481            [DllImport("Gdi32.dll")]
482            public extern static IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
483
484            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
485            public static extern IntPtr GetThemeMargins(SafeThemeHandle hTheme,
486                IntPtr hdc, int iPartId, int iStateId, int iPropId, ref Rectangle prc,
487                ref Rectangle pMargins);
488
489            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
490            public static extern IntPtr GetThemeMargins(SafeThemeHandle hTheme,
491                IntPtr hdc, int iPartId, int iStateId, int iPropId, IntPtr prc,
492                ref Rectangle pMargins);
493
494            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
495            public static extern IntPtr GetThemePartSize(SafeThemeHandle hTheme,
496                IntPtr hdc, int iPartId, int iStateId, ref Rectangle prc,
497                THEMESIZE eSize, ref Size psz);
498
499            [DllImport("UxTheme.dll", CharSet = CharSet.Unicode)]
500            public static extern IntPtr GetThemePartSize(SafeThemeHandle hTheme,
501                IntPtr hdc, int iPartId, int iStateId, IntPtr prc,
502                THEMESIZE eSize, ref Size psz);
503
504            public enum MENUPARTS
505            {
506                MENU_MENUITEM_TMSCHEMA = 1,
507                MENU_MENUDROPDOWN_TMSCHEMA = 2,
508                MENU_MENUBARITEM_TMSCHEMA = 3,
509                MENU_MENUBARDROPDOWN_TMSCHEMA = 4,
510                MENU_CHEVRON_TMSCHEMA = 5,
511                MENU_SEPARATOR_TMSCHEMA = 6,
512                MENU_BARBACKGROUND = 7,
513                MENU_BARITEM = 8,
514                MENU_POPUPBACKGROUND = 9,
515                MENU_POPUPBORDERS = 10,
516                MENU_POPUPCHECK = 11,
517                MENU_POPUPCHECKBACKGROUND = 12,
518                MENU_POPUPGUTTER = 13,
519                MENU_POPUPITEM = 14,
520                MENU_POPUPSEPARATOR = 15,
521                MENU_POPUPSUBMENU = 16,
522                MENU_SYSTEMCLOSE = 17,
523                MENU_SYSTEMMAXIMIZE = 18,
524                MENU_SYSTEMMINIMIZE = 19,
525                MENU_SYSTEMRESTORE = 20,
526            }
527
528            public enum POPUPCHECKSTATES
529            {
530                MC_CHECKMARKNORMAL = 1,
531                MC_CHECKMARKDISABLED = 2,
532                MC_BULLETNORMAL = 3,
533                MC_BULLETDISABLED = 4,
534            }
535
536            public enum POPUPCHECKBACKGROUNDSTATES
537            {
538                MCB_DISABLED = 1,
539                MCB_NORMAL = 2,
540                MCB_BITMAP = 3,
541            }
542
543            public enum POPUPITEMSTATES
544            {
545                MPI_NORMAL = 1,
546                MPI_HOT = 2,
547                MPI_DISABLED = 3,
548                MPI_DISABLEDHOT = 4,
549            }
550
551            public enum POPUPSUBMENUSTATES
552            {
553                MSM_NORMAL = 1,
554                MSM_DISABLED = 2,
555            }
556
557            public enum TMT_MARGINS
558            {
559                TMT_SIZINGMARGINS = 3601,
560                TMT_CONTENTMARGINS,
561                TMT_CAPTIONMARGINS
562            }
563
564            public enum THEMESIZE
565            {
566                TS_MIN,
567                TS_TRUE,
568                TS_DRAW
569            }
570        }
571    }
572
573    internal class SafeThemeHandle : SafeHandle
574    {
575        public SafeThemeHandle()
576            : base(IntPtr.Zero, true)
577        {
578        }
579
580        public override bool IsInvalid
581        {
582            get { return handle == IntPtr.Zero; }
583        }
584
585        protected override bool ReleaseHandle()
586        {
587            UXThemeMenuRenderer.NativeMethods.CloseThemeData(handle);
588            handle = IntPtr.Zero;
589            return true;
590        }
591    }
592}
Note: See TracBrowser for help on using the repository browser.