Monday, April 25, 2011

Style Browser for WPF

Introduction

In this article I will show you simple application that can help you navigate in mass of your XAML styles. You can download sources from attachments to current article

My original article on codeproject, where you can download sources: http://www.codeproject.com/KB/WPF/stylebrowser.aspx

Background 

I am sure each WPF developer have met following problem: a lot of styles...a lot of images, brushes, different textboxes and textblocks. And sometimes it is hard to find resource key you need. This problem is often met in large WPF projects, with a lot of custom controls. When there are multiple developers - it can be much more hard! But for some cases - there is rather simple solution. Application described in this article can help you to make this process easier. Using StyleBrowser you can select XAML and look "inside" it. Application will show you all styles, images and brushes are stored in specified XAML file.
Also this application can be used when you want to use styles that you have created before and dony remember what exactly is inside XAML file. Just open XAML, find needed style and use it! 

Using the application

When you open application you see follwoing screen:

 Push "Select XAML" and open-dialog will appears on your desktop. Select XAML file with styles and press OK. If XAML is able to parse, then you will see something like this:

In the middle of application there is TabControl with number of tabs. Each tabs is a group of some styles from XAML. On screenshot tab "Buttons" is opened and you can see buttons from XAML.  "Sample content" - is added automatically to each control that has "Content" property. Reflection is used for detection.  To the right there is style Key and its classname. All styles are sorted A-Z.
Also, there is 2 usefull features, that can help you. You can select background, because some styles are white, some black...some black and white :) . Right now there is only 3 predefined colors. Black, white and gray. I think that it will be something like color picker in the future.
The second feature is searching. Just type any word in search textbox and you will see only styles that matches searched text. Color and search is show on following image

 Last interesing usefull feature is that you can see XAML. Just click on "XAML" button to the right of style you want to see. But this feature has one disadvantage at current step. It show you XAML with fully qualified classnames and also XML namespaces are added. But simple XAML's looks nice and can be usefull.


Code screenshots  

Lets make a short trip through code and take look ate how it works. At the beginning OpenDialog is used to selected file.
To parse XAML I simply use following code:

XamlReader reader = new XamlReader();
             FileStream stream = new FileStream(dlg.FileName, FileMode.Open);
             Uri uri = new Uri((new FileInfo(dlg.FileName)).Directory.FullName + "\\");
             object xaml = reader.LoadAsync(stream, new ParserContext() { BaseUri = uri });
 
We have to specify BaseUri here, so parser is able to process relative paths to images or any other files, if they exists in XAML. 
This code also has disadvantage. If some of tag cant be parsed, XAML will not be read at all. So here we got restrictions: XAML should contains only namespaces defined by default in .Net version sources was compilled in. My solution uses .Net 4.0.
Also, you should be carefull with images pathes. 
Next step is to fill resources of current window with loaded styles. We need it because there can be some styles that use another styles from that XAML. If we dont copy XAML to the resources -then referenced XAML will not loaded.

this.Resources.Clear();
                    if (xaml is ResourceDictionary)
                    {
                        foreach (DictionaryEntry item in (xaml as ResourceDictionary))
                        {
                            this.Resources.Add(item.Key, item.Value);
                            entries.Add(item);                           
                        }
                    }
Right now this code has another bad thing: sometimes some control of  main window that has style in resources will change it's look, even if  this control is not in preview area. I am going to fix it in future.
So, we have collection on styles, brushes and images, We can do everything with it. Sort, group, etc. In my app styles a sorted A-Z.
Next task is go through each item in collection and create preview in related tab control.

foreach (var item in list.OrderBy(e => e.Key.ToString()))
            {
                StyleItem styleItem = null;
                DictionaryEntry entry = (DictionaryEntry)item;
                if (entry.Value is Style)
                {
                    Style style = entry.Value as Style;
                    Type type = style.TargetType;
                    object obj = Activator.CreateInstance(type); 
if (obj is AnimationTimeline) continue;

                    styleItem = new StyleItem(obj as FrameworkElement, style, type, entry.Key.ToString());

                    if (type == typeof(Button) || type == typeof(ToggleButton))
                    {
                        buttonsPanel.Children.Add(styleItem);
                    } 
 
StyleItem is custom control that represent style preview. Each DictionaryEntry has Key and Value (as ususal has Dictionary :) ). Key is a x:Key attribute from XAML. Value is an content of XML-element.
We have to check what type  value is, because, for example you cant apply Style property if current item is an Image. Also, we need to skip any Animation, because seems it is impossible to create universal preview for animations. If item is brush or image I just assign background of preview as image or brush.
If item is style, there are 3 important lines, which extract style, object type and create instance of extracted type.

Style style = entry.Value as Style;
                    Type type = style.TargetType;
                    object obj = Activator.CreateInstance(type); 
 
Then StyleItem use this info to create related preview item in TabControl.
Another interesting thing is how preview XAML text is created.

string xaml;
                    try
                    {
                        xaml = XamlWriter.Save(entry.Value).Replace(">", ">\n").NormalizeXaml();
                    }
                    catch { xaml = "XAML cannot be parsed"; }
 
Why "Replace" method is used here? Because Save() return string without line breaks. So, I decided to insert line break after each ">".
Also, there is extension method for String:

NormalizeXaml() 
 
This method insert spaces at the beginning of each line depends on it deep in XML. I am not sure if I should show this method implementation in this article. 

Restrictions and future  

As for now, there is a number of issues in this application. But if you will find it usefull, please tell me about that. If developers will like what this app doing - I will work on it and add new usefull features and fix current issues.
In future I am going to:
- Skip styles with errors
- Add DLL's with resources
- Feature to add different XAML's. For example if one XAML has references to styles defined in another
- Add color picker to choose custom background color
- Clean XAML source viewer from namespaces and fullqualified pathes
- Create more smart object creator. For example: use predefined content template for menus, tabcontrols and other controls that has complicate content
- Make preview areas resizable. For example small text need small rectangle, but slider or image sometimes need huge area
- Maybe you will tell me abot any interestinf features


No comments:

Post a Comment