How to change the theme at run-time

6 posts, 0 answers
  1. Patrick
    Patrick avatar
    372 posts
    Member since:
    Aug 2012

    Posted 17 Feb 2012 Link to this post

    Requirements

    RadControls version Q1 2012
    .NET version Silverlight 4 / 5
    Visual Studio version 2010
    programming language Oxygene (Delphi Prism)
    browser support

    all browsers supported by RadControls


    PROJECT DESCRIPTION
    Many people are asking about changing the theme at run-time. Setting the application theme defines the theme for the new controls, but existing controls are not changed. The code below traverse the whole controls tree to set the theme of each control.
    It works well in the test case I made, excepted for the Office_Black that seems to kill the Silverlight application.
    To have all the possibilities, I had to include theMicrosoft.Internal.Pivot.Controls DLL, because it defines a HeaderedContentControl that I need to check. If you don't use this DLL, you can comment the code relative to this class and remove the referenced DLL.

    Namespace OrdinaSoft.Windows.Controls;
     
     
    (* Extensions to the Telerik Theme class.
     
       Author:  OrdinaSoft
                Patrick Lanz
                Lausanne
                info@ordinasoft.ch
     
       First version: February 17, 2012
    *)
     
     
    Interface
     
     
      Uses
        System.Windows,
        System.Windows.Media,
     
        Telerik.Windows.Controls;
     
    (*---------------------------------------------------------------------------------------------*)
     
      Type
     
        /// <summary>
        ///   Extensions to the Telerik
        ///   class.
        /// </summary>
        /// <remarks>
        ///   This class defines a method that lets change the theme of the application at run-time.
        /// </remarks>
        osTheme = Public Class (
          Theme
        )
     
        Private
     
        {-- Method --}
     
          /// <summary>
          ///   Traverse the visual tree from a given object and set the theme on encountered
          ///   objects.
          /// </summary>
          /// <param name="Root">
          ///   Root from which we must traverse the visual tree. Can be <b>null</b>.
          /// </param>
          /// <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
          Class Method ChangeApplicationTheme (Root : DependencyObject; NewTheme : Theme);
     
        Public
     
        {-- Method --}
     
          /// <summary>
          ///   Changes the application theme at run-time.
          /// </summary>
          /// <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
          /// <remarks>
          ///   In addition to setting the application theme, this method will change the theme of
          ///   all the controls in the application.
          /// </remarks>
          Class Method ChangeApplicationTheme (NewTheme : Theme);
     
        End;
     
    (*---------------------------------------------------------------------------------------------*)
     
    Implementation
     
    (*---------------------------------------------------------------------------------------------*)
    (* Application theme. *)
     
      // <summary>
      //   Changes the application theme at run-time.
      // </summary>
      // <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
      // <remarks>
      //   In addition to setting the application theme, this method will change the theme of
      //   all the controls in the application.
      // </remarks>
     
      Class Method osTheme.ChangeApplicationTheme (
        NewTheme : Theme
      );
     
      Begin
        StyleManager.ApplicationTheme := NewTheme;
        ChangeApplicationTheme (Application.Current.RootVisual, NewTheme)
      End;
     
     
     
      // <summary>
      //   Traverse the visual tree from a given object and set the theme on encountered
      //   objects.
      // </summary>
      // <param name="Root">
      //   Root from which we must traverse the visual tree. Can be <b>null</b>.
      // </param>
      // <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
     
      Class Method osTheme.ChangeApplicationTheme (
        Root     : DependencyObject;
        NewTheme : Theme
      );
     
      Begin
        If Not Assigned (Root) Then
          Exit;
        StyleManager.SetTheme (Root, NewTheme);
     
        {-- System.Windows.Controls.ItemsControl --}
        With Matching Control := System.Windows.Controls.ItemsControl (Root) Do Begin
          For Matching Item : DependencyObject In Control.Items Do
            ChangeApplicationTheme (Item, NewTheme);
          Exit
        End;  {With Matching Control := System.Windows.Controls.ItemsControl (Root)}
     
        {-- Microsoft.Internal.Pivot.Controls.HeaderedContentControl --}
        With Matching Control := Microsoft.Internal.Pivot.Controls.HeaderedContentControl (Root) Do
          ChangeApplicationTheme (DependencyObject (Control.Header),  NewTheme);
     
        {-- Telerik.Windows.Controls.HeaderedContentControl --}
        With Matching Control := Telerik.Windows.Controls.HeaderedContentControl (Root) Do
          ChangeApplicationTheme (DependencyObject (Control.Header), NewTheme);
     
        {-- System.Windows.Controls.ContentControl --}
        With Matching Control := System.Windows.Controls.ContentControl (Root) Do Begin
          ChangeApplicationTheme (DependencyObject (Control.Content), NewTheme);
          Exit
        End;  {With Matching Control := System.Windows.Controls.ContentControl (Root)}
     
        {-- Traverse the children --}
        For ix : Int32 := 0 To VisualTreeHelper.GetChildrenCount (Root) - 1 Do
          ChangeApplicationTheme (VisualTreeHelper.GetChild (Root, ix), NewTheme)
     
      End;
     
    (*---------------------------------------------------------------------------------------------*)
     
    End.

    In case of somebody has a solution to the Office_Black theme, here is the error message from Firefox:
    Erreur : Unhandled Error in Silverlight Application
    Code: 4004   
    Category: ManagedRuntimeError      
    Message: System.Exception: Error HRESULT E_FAIL has been returned from a call to a COM component.
       at MS.Internal.XcpImports.CheckHResult(UInt32 hr)
       at MS.Internal.XcpImports.SetValue(IManagedPeerBase obj, DependencyProperty property, String s)
       at MS.Internal.XcpImports.SetValue(IManagedPeerBase doh, DependencyProperty property, Object obj)
       at System.Windows.DependencyObject.SetObjectValueToCore(DependencyProperty dp, Object value)
       at System.Windows.DependencyObject.SetEffectiveValue(DependencyProperty property, EffectiveValueEntry& newEntry, Object newValue)
       at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)
       at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet, Boolean isBindingInStyleSetter)
       at System.Windows.ResourceDictionary.set_Source(Uri value)
       at Telerik.Windows.Controls.Theme.LoadGenericIfNeeded(String controlAssembly)
       at Telerik.Windows.Controls.Theme.GetResourceFromTheme(Theme theme, String controlAssembly, Type defaultStyleKeyType, Theme controlTheme)
       at Telerik.Windows.Controls.Theme.GetResourceValue(Type defaultStyleKey, Theme theme, Theme controlTheme)
       at Telerik.Windows.Controls.Theme.GetThemeStyle(Theme oldTheme, Type defaultStyleKey)
       at Telerik.Windows.Controls.Theme.Apply(FrameworkElement element, Theme oldTheme)
       at Telerik.Windows.Controls.StyleManager.OnThemeChanged(DependencyObject target, DependencyPropertyChangedEventArgs changedEventArgs)
       at Telerik.Windows.PropertyMetadata.PropertyChangeHook.OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
       at System.Windows.DependencyObject.RaisePropertyChangeNotifications(DependencyProperty dp, Object oldValue, Object newValue)
       at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)
       at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet, Boolean isBindingInStyleSetter)
       at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
       at Telerik.Windows.Controls.StyleManager.SetTheme(DependencyObject element, Theme value)
       at OrdinaSoft.Windows.Controls.osTheme.ChangeApplicationTheme(DependencyObject Root, Theme NewTheme)
       at OrdinaSoft.Windows.Controls.osTheme.ChangeApplicationTheme(Theme NewTheme)
       at TestThemes.MainPage.SetTheme(Theme Theme)
       at TestThemes.MainPage.SetOfficeBlackTheme(Object sender, RoutedEventArgs e)
       at System.Windows.Controls.Primitives.ButtonBase.OnClick()
       at System.Windows.Controls.Primitives.ToggleButton.OnClick()
       at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
       at Telerik.Windows.Controls.RadRadioButton.OnMouseLeftButtonUp(MouseButtonEventArgs e)
       at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)
       at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName, UInt32 flags)    

    The test cas I made can be seen at http://www.ordinasoft.ch/telerik/RadSL/TestThemes/TestThemesTestPage.html

    Patrick
  2. Vanya Pavlova
    Admin
    Vanya Pavlova avatar
    2019 posts

    Posted 21 Feb 2012 Link to this post

    Hello Patrick,

     

    Thank you for sharing this application in the form of Code Library with our community! 
    I have updated your Telerik points accordingly! 



    Greetings,
    Vanya Pavlova
    the Telerik team
    Sharpen your .NET Ninja skills! Attend Q1 webinar week and get a chance to win a license! Book your seat now >>
  3. Patrick
    Patrick avatar
    372 posts
    Member since:
    Aug 2012

    Posted 21 Feb 2012 Link to this post

    Hello, Vanya,
    I have an updated version that corrects the problem in Office_Black and manages more special cases. But I need to still check some little things before publishing it.
    If I have the time, I will publish it at the end of the week.
    Patrick
  4. TMatt
    TMatt avatar
    46 posts
    Member since:
    Feb 2011

    Posted 28 Apr 2012 Link to this post

    Still wait for it :)
  5. Patrick
    Patrick avatar
    372 posts
    Member since:
    Aug 2012

    Posted 28 Apr 2012 Link to this post

    TMatt,
    unfortunately, some Telerik controls doesn't accept the dynamic change of the theme: some just forgets their children and other change properties!
    Here are the current version, with the above restrictions:
    Namespace OrdinaSoft.Windows.Controls;
     
     
    (* Extensions of the Telerik Theme class.
     
       Author:  OrdinaSoft
                Patrick Lanz
                Lausanne
                info@ordinasoft.ch
     
       First version: February 17, 2012
    *)
     
     
    Interface
     
     
      Uses
        System.Collections.Generic,
        System.Reflection,
        System.Windows,
        System.Windows.Controls,
        System.Windows.Media,
     
        Telerik.Windows.Controls;
     
    (*---------------------------------------------------------------------------------------------*)
     
      Type
     
        /// <summary>
        ///   Extensions of the Telerik
        ///   class.
        /// </summary>
        /// <remarks>
        ///   This class defines a method that lets change the theme of the application at run-time.
        /// </remarks>
        osTheme = Public Class (
          Theme
        )
     
        Private Class
     
        {-- Field --}
     
          /// <summary>
          ///   Set of the forbidden classes: those on which we must not set the theme.
          /// </summary>
          ForbiddenClassesNames : HashSet <String>; ReadOnly;
     
        Private
     
        {-- Class constructor --}
     
          /// <summary>
          ///   Initializes the static data of the class.
          /// </summary>
          Class Constructor;
     
        {-- Methods --}
     
          /// <summary>
          ///   Traverse the visual tree from a given object and set the theme on encountered
          ///   objects.
          /// </summary>
          /// <param name="Root">
          ///   Root from which we must traverse the visual tree. Can be <b>null</b>.
          /// </param>
          /// <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
          Class Method ChangeTheme (Root : DependencyObject; NewTheme : Theme);
     
          /// <summary>
          ///   Gets a value that indicates whether an object is of a forbidden type.
          /// </summary>
          /// <param name="Obj">The object to check.</param>
          /// <returns>
          ///   <b>true</b> if <paramref name="Obj" /> is a forbidden type;<br />
          ///   otherwise, <b>false</b>.
          /// </returns>
          /// <remarks>
          ///   <para>
          ///     An object is of a forbidden type if the name of its type, or the name of the type
          ///     of one of its ancestors is in <see cref="ForbiddenClassesNames" />.
          ///   </para>
          ///   <para>
          ///     We check the name of the type and not the type, because we can support types
          ///     without loading the supporting assemblies.
          ///   </para>
          /// </remarks>
          Class Method IsForbiddenType (Obj : DependencyObject) : Boolean;
     
        Public
     
        {-- Method --}
     
          /// <summary>
          ///   Changes the application theme at run-time.
          /// </summary>
          /// <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
          /// <remarks>
          ///   In addition to setting the application theme, this method will change the theme of
          ///   all the controls in the application.
          /// </remarks>
          Class Method ChangeApplicationTheme (NewTheme : Theme);
     
        End;
     
    (*---------------------------------------------------------------------------------------------*)
     
    Implementation
     
    (*---------------------------------------------------------------------------------------------*)
    (* Class constructor. *)
     
      // <summary>
      //   Initializes the static data of the class.
      // </summary>
     
      Class Constructor osTheme;
     
      Begin
        ForbiddenClassesNames := New HashSet <String>;
        ForbiddenClassesNames.Add ('System.Windows.Controls.UserControl'       );
        ForbiddenClassesNames.Add ('Telerik.Windows.Controls.RadDocking'       );
        ForbiddenClassesNames.Add ('Telerik.Windows.Controls.RadPane'          );
        ForbiddenClassesNames.Add ('Telerik.Windows.Controls.RadPanelBar'      );
        ForbiddenClassesNames.Add ('Telerik.Windows.Controls.RadPanelBarItem'  );
        ForbiddenClassesNames.Add ('Telerik.Windows.Controls.RadPaneGroup'     );
        ForbiddenClassesNames.Add ('Telerik.Windows.Controls.RadSplitContainer')
      End;
     
    (*---------------------------------------------------------------------------------------------*)
    (* Forbidden types management. *)
     
      // <summary>
      //   Gets a value that indicates whether an object is of a forbidden type.
      // </summary>
      // <param name="Obj">The object to check.</param>
      // <returns>
      //   <b>true</b> if <paramref name="Obj" /> is a forbidden type;<br />
      //   otherwise, <b>false</b>.
      // </returns>
      // <remarks>
      //   <para>
      //     An object is of a forbidden type if the name of its type, or the name of the type
      //     of one of its ancestors is in <see cref="ForbiddenClassesNames" />.
      //   </para>
      //   <para>
      //     We check the name of the type and not the type, because we can support types
      //     without loading the supporting assemblies.
      //   </para>
      // </remarks>
     
      Class Method osTheme.IsForbiddenType (
        Obj : DependencyObject
      ) : Boolean;
     
      Begin
        If Assigned (Obj) Then Begin
          Var &Type := Obj.GetType;
          Repeat
            If ForbiddenClassesNames.Contains (&Type.FullName) Then
              Exit True;
            &Type := &Type.BaseType
          Until Not Assigned (&Type)
        End;  {Assigned (Obj)}
        Exit False
      End;
     
    (*---------------------------------------------------------------------------------------------*)
    (* Application theme. *)
     
      // <summary>
      //   Changes the application theme at run-time.
      // </summary>
      // <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
      // <remarks>
      //   In addition to setting the application theme, this method will change the theme of
      //   all the controls in the application.
      // </remarks>
     
      Class Method osTheme.ChangeApplicationTheme (
        NewTheme : Theme
      );
     
      Begin
        StyleManager.ApplicationTheme := NewTheme;
        ChangeTheme (Application.Current.RootVisual, NewTheme)
      End;
     
     
     
      // <summary>
      //   Traverse the visual tree from a given object and set the theme on encountered
      //   objects.
      // </summary>
      // <param name="Root">
      //   Root from which we must traverse the visual tree. Can be <b>null</b>.
      // </param>
      // <param name="NewTheme">The new application theme. Can be <b>null</b>.</param>
     
      Class Method osTheme.ChangeTheme (
        Root     : DependencyObject;
        NewTheme : Theme
      );
     
      Begin
        If Not Assigned (Root) Then
          Exit;
     
        // Check for controls on which we can't set the theme.
        If Not IsForbiddenType (Root) Then
          StyleManager.SetTheme (Root, NewTheme);
     
        {-- Telerik.Windows.Controls.RadDocking --}
    //    With Matching Control := Telerik.Windows.Controls.RadDocking (Root) Do
    //      ChangeTheme (DependencyObject (Control.DocumentHost), NewTheme);
     
        {-- System.Windows.Controls.ItemsControl --}
        With Matching Control := System.Windows.Controls.ItemsControl (Root) Do Begin
          For Matching Item : DependencyObject In Control.Items Do
            ChangeTheme (Item, NewTheme);
          Exit
        End;  {With Matching Control := System.Windows.Controls.ItemsControl (Root)}
     
        {-- Control with a header --}
        With Matching PropInfo := Root.GetType.GetProperty ('Header') Do
          ChangeTheme (DependencyObject (PropInfo.GetValue (Root, Nil)), NewTheme);
     
        {-- System.Windows.Controls.ContentControl --}
        With Matching Control := System.Windows.Controls.ContentControl (Root) Do Begin
          ChangeTheme (DependencyObject (Control.Content), NewTheme);
          Exit
        End;  {With Matching Control := System.Windows.Controls.ContentControl (Root)}
     
        {-- System.Windows.Controls.Border --}
        With Matching Control := System.Windows.Controls.Border (Root) Do Begin
          ChangeTheme (Control.Child, NewTheme);
          Exit
        End;  {With Matching Control := System.Windows.Controls.Border (Root)}
     
        {-- Traverse the children --}
        For ix : Int32 := 0 To VisualTreeHelper.GetChildrenCount (Root) - 1 Do
          ChangeTheme (VisualTreeHelper.GetChild (Root, ix), NewTheme)
     
      End;
     
    (*---------------------------------------------------------------------------------------------*)
     
    End.
    When a new RadControls version corrects some of the problems, I will update the code.
    I hope it will help you.
    Patrick
  6. TMatt
    TMatt avatar
    46 posts
    Member since:
    Feb 2011

    Posted 28 May 2012 Link to this post

    thanks
Back to Top