I’ve made a SoapBox Core application that works great but looks typical. How can I easily improve the overall look of my application?

asked 19 Oct '10, 22:20

KarlB's gravatar image

KarlB
576202136
accept rate: 0%

edited 29 Nov '10, 23:12

Scott%20Whitlock's gravatar image

Scott Whitlock ♦♦
696262833


I've been looking into this since the weekend. I've tried to nail an approach for implementing theme support before going any further writing new controls etc. Provisionally, this is what I have so far, and it appears to be working well for now. Note: currently I'm only concerned with a base theme that offers the ability to change the colour scheme.

First, a little bit of background:

My solution is organised as suggested (See Creating an Add-in) and I have the following projects under my own namespace:

Core\
    [namespace].Contracts (as you'd expect)
    [namespace].Controls (contains usercontrols)
Modules\
    Common\
        [namespace].Modules.Themes

With the Themes project having a dependency on the other two, I have defined ITheme and IThemeService in Contracts:

public interface ITheme
{
    String InternalName { get; }
    String DisplayName { get; }
}

public interface IThemeService
{
    IEnumerable<ITheme> Themes { get; set; }
    void ApplyTheme();
    void ApplyTheme(ITheme theme);
    ITheme GetThemeByInternalName(String internalName);
}

Now, as for the Themes Module...

alt text

The ThemesCommonControls folder holds an individual ResourceDictionary for each control, defining it's style etc (as you can see I've just been playing around with some custom buttons). Then there is a folder for each colour scheme (Default and Green so far), inside of which is the [name]Theme.xaml ResourceDictionary that merges those found in ThemesCommonControls. You'll also notice a Sample.[name].xaml file - this is simply a usercontrol, for a convenient way to view some dummy themed controls in the design view (to observe changes quickly). The main named Theme.xaml dictionaries are exported, like so:

[Export(SoapBox.Core.ExtensionPoints.Host.Void, typeof(ITheme))]
public partial class DefaultTheme : ResourceDictionary, ITheme

The ThemeManager (it should probably be renamed) implements IThemeService, and is itself exported...

[Export(SoapBox.Core.ExtensionPoints.Host.Void, typeof(IThemeService))]
public class ThemeManager : IThemeService

...and imports the Many IThemes available:

[ImportMany(SoapBox.Core.ExtensionPoints.Host.Void, typeof(ITheme))]
public IEnumerable<ITheme> Themes { get; set; }

The user will select the desired theme in the OptionsDialog (ThemeOptionsPad), via a combo. This Pad imports the IThemeService, which can be used to query and bind to the available themes. The InternalName of an ITheme is persisted, whereas the DisplayName is the appealing name that the user will come to know it as - 'Snot Green', 'Badger Black', whatever.

The SetTheme StartupCommand calls IThemeService.ApplyTheme(), which applies the persisted Theme when your app runs (you'll have set the default value of this setting to 'Default').

Finally... hopefully you're still with me at this point... some inner workings of the ThemeManager:

public void ApplyTheme(ITheme theme)
{
    ResourceDictionary dictionary = GetThemeResourceDictionary(theme.InternalName);
    // Guard against null omitted
    Application.Current.Resources.MergedDictionaries.Add(dictionary);
}

private ResourceDictionary GetThemeResourceDictionary(String internalName)
{
    string path = String.Format(@"{0}/{0}Theme.xaml", internalName);
    return GetNamedResourceDictionary(path);
}

private ResourceDictionary GetNamedResourceDictionary(String path)
{
    String packUri = String.Empty;
    try
    {
        if (path != null)
        {
            packUri = String.Format(@"/[namespace].Modules.Themes;component/Themes/" + path);
                return Application.LoadComponent(new Uri(packUri, UriKind.Relative)) as ResourceDictionary;
        }
    }
    catch
    {
        this.MessagingService.Value.ShowMessage("Unable to locate theme.", "Failed to load theme");
    }
    return null;
}

Oh, and of course: inside each [name]Theme.xaml you define several Color and SolidColorBrush resources with the same generic names in each Theme (Colour_1, Colour_2, Colour_highlight etc) that are referred to as DynamicResources in the individual controls' ResourceDictionary as appropriate.

<Style TargetType="{x:Type controls:ImageToggleButton}">
    <Setter Property="Background" Value="{DynamicResource Colour1}"/>
    <!-- Blah, blah, blah -->
</Style>
link

answered 20 Oct '10, 16:14

Lee's gravatar image

Lee
1413311
accept rate: 0%

edited 22 Oct '10, 08:20

Any comments/suggestions are welcome. Like I said, this is a work in progress of the last few evenings and is probably just one of many ways of doing this type of thing. I'll let you know how it goes...

(20 Oct '10, 16:24) Lee

Lee: I would be very interested in taking a closer look at your ThemesManager add-in if you were willing to share.

(20 Oct '10, 22:46) KarlB

This is a very interesting post, Lee. I was a bit surprised that you're exporting everything to the Host.Void contract, and then resolving by type, but it makes sense for services. It makes it very container-like. Is this something that can be generalized for any SBC app, or is it specific to yours?

(21 Oct '10, 19:59) Scott Whitlock ♦♦

Applying styles and triggers to an application to achieve a certain theme or skin across all controls/windows is a simple way to greatly enhance the visual appearance of an application. This is especially easy to do in a WPF application like SoapBox Core that use an MVVM pattern throughout. In-order to maximize consistency and maintainability of your themes across the application it is recommended that you place all styles and triggers in a single resource dictionary Called ‘Styles.xaml’ somewhere in the project (any ideas where’s best to put such a file?). This file would of course then need to be imported and stuck into the visual tree somewhere within the application, any ideas for how best to do that?

There is a lot out there on styles and themes. More than I could helpfully explain here. The key point is that including styles and themes can be made simple in SoapBox Core. I’ll try to create an example or tutorial that dives deeper into this sometime soon.

While I hope such an explanation will be help those of you who are interested out there, I must warn you that I am not much of an artist myself. While styles and themes should be used to make your application look better, I will almost surely make this one look worse. Maybe you’ll be able to do a better job than I.

In-fact, maybe after we add a common support mechanism for utilizing styles and triggers in SoapBox Core we can have a competition to see who can come up with the best theme? What do you think? Tell your artistic friends.

link

answered 19 Oct '10, 22:22

KarlB's gravatar image

KarlB
576202136
accept rate: 0%

I like Lee's approach very much. His implementation of GetThemeResourceDictionary(String internalName) takes a convention over configuration approach that is very caliburn-esque (btw, have you seen this article on using caliburn with MEF?). Also, I think that ThemeManager is an appropriate name for the class that implements IThemeService.

Lee's approach goes far beyond what I originally had in mind when I asked this question. Being able to change color schemes and/or entire themes at run time is great in some cases, but in others (like in my case, for example) all the developer wants to be able to do is assert a certain feel - maybe with some company branding - across an application. For this dumbed down functionality, I think the original answer is a dumbed down, simplier and possibly more appropriate approach.

If all you want to do is insert some branding uniformly across all of your views then you can simply:

  1. Create a new ResourceDictionary in the SoapBox.Core.Host project and name it 'Styles.xaml'
  2. Define all of your styles that are to be shared and used throughout the application in your newly create 'Styles.xaml' file.
  3. Finally Add the < ResourceDictionary Source="Styles.xaml > attribute inbetween the < Application.Resources > section of the App.xaml file in the SoapBox.Core.Host project.

That's it! that's all you have to do. You won't even have to export the Styles.xaml ResourceDictionary as I had originally suggested.

When I do this, I like to use TargetType instead of Key when defining my styles. This way, whenever a new pad or document is imported, it is automatically styled the way I want. Of course this has it's draw backs since rarely does it look good when EVERY control has the EXACT style. This sometimes means that I need to slightly alter some styles but this is easy to do since the styles held on the Application.Resources level are ignored when lower level resources are defined.

As stated previously, I think the two approached brought forth thus far are both very good depending on your needs. What do you think of adding this simple 'Styles.xaml' file approach to the main branch of SoapBox Core? Also, what do you think of this idea for coming up with a way for Lee to share his more robust approch with the rest of the community - if he were willing to do so. (I think alot of people could benefit from the functionality provided by Lee's approach but since it is so complex and invovled, it may not properly fit in the core - at least not all of it. Maybe the contract definitions could be added to the core? )

What do you guys think?

-Karl

link

answered 20 Oct '10, 22:46

KarlB's gravatar image

KarlB
576202136
accept rate: 0%

Right now there are two sets of resource dictionaries imported in the host and merged into the Application Resources: Styles and Views. The only difference is that dictionaries imported through the Styles extension point are guaranteed to be merged in Before ones that get pulled in through Views. That's supposed give you a way to satisfy dependencies. We could easily add a Theme extension point that only imports one (optional) resource dictionary and merges it in first. That should provide what you're describing here without the need to go in and edit the host directly.

(21 Oct '10, 20:05) Scott Whitlock ♦♦
Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Markdown Basics

  • *italic* or _italic_
  • **bold** or __bold__
  • link:[text](http://url.com/ "Title")
  • image?![alt text](/path/img.jpg "Title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Tags:

×17
×11
×5
×1
×1
×1
×1

Asked: 19 Oct '10, 22:20

Seen: 3,567 times

Last updated: 29 Nov '10, 23:12

powered by OSQA