Learning WPF with BabySmash - Keeping it DRY with XAML Styles
NOTE: If you haven't read the first post in this series, I would encourage you do to that first, or check out the BabySmash category. Also check out http://windowsclient.net/ for more developer info on WPF.
BACKGROUND: This is one of a series of posts on learning WPF. I wrote an application for my 2 year old using WPF, but as I'm a Win32-minded programmer, my working app is full of Win32-isms. It's not a good example of a WPF application even though it uses the technology. I'm calling on community (that's you, Dear Reader) to blog about your solutions to different (horrible) selections of my code. You can get the code http://www.codeplex.com/babysmash. Post your solutions on your blog, in the comments, or in the Issue Tracker and we'll all learn WPF together. Pick a single line, a section, or subsystem or the whole app!
BabySmash is full of shapes and stuff and those shapes a similar look. They have the same stroke (outline color) and stroke thickness (outline width) for example.
There are a lot of places in my code where I not only repeat the assignment of these styles.
Shape = Shape = new Path()
{
Data = MakeCharacterGeometry(nameToDisplay),
Fill = fill,
Stroke = Brushes.Black,
StrokeThickness = 5,
Height = 400
};
This kind of object initializer code is copy/pasted all over. It's not DRY. (Don't Repeat Yourself) First I started looking for object oriented ways to solve this issue. I figured I'd put in some base class that would do the work, or make a Utility (gasp!) class to do this tedium.
Ideally I wanted this stuff in one place (hence DRY) and I wanted to be able to apply it to my shapes. Later, I realized I also wanted to occasionally apply these properties to some shapes and not others. At that point, my object-oriented idea started to fall down.
I felt (and still feel) like most of the Shape stuff for BabySmash belongs in the XAML markup, and folks on CodePlex agreed. Sherwin Rice kindly pointed me towards Styles.
He suggested storing these properties in named bundles of styles in the XAML. This gives me the arbitrary flexibility I needed.
<Style x:Key="circle" TargetType="Ellipse">
<Setter Property="Width" Value="400"/>
<Setter Property="Height" Value="400"/>
<Setter Property="StrokeThickness" Value="5"/>
<Setter Property="Stroke" Value="Black"/>
</Style>
However, as I started moving most of my shape's details over into XAML, things started repeating again. I was trading one kind of "markup" (the C# kind) for another. Poop. Well, turns out you can base styles on styles, so I was able to keep it DRY again.
<Style x:Key="BabySmashBaseStyle" TargetType="Shape">
<Setter Property="StrokeThickness" Value="5"/>
<Setter Property="Stroke" Value="Black"/>
</Style>
<Style x:Key="trapezoid" TargetType="Path"
BasedOn="{StaticResource BabySmashBaseStyle}">
<Setter Property="Data" Value="F1 M 257.147,126.953L 543.657,126.953L 640.333,448.287L 160.333,448.287L 257.147,126.953 Z"/>
</Style>
<Style x:Key="star" TargetType="BabySmash:Star"
BasedOn="{StaticResource BabySmashBaseStyle}">
<Setter Property="NumberOfPoints" Value="5"/>
<Setter Property="Width" Value="400"/>
<Setter Property="Height" Value="400"/>
</Style>
These all live in <Application.Resources> and I can apply them as I like:
Shape s = new Ellipse();
s.Style = Application.Current.Resources[Name] as Style;
I appear to have just touched the surface of Styles, but I'm continuing to dig in. WPF is starting to make sense to me. Just a smidge.
Related Links
- There's an older, but excellent introduction to WPF Styles by Gill Cleeren on MSDN Belgium.
- There are also over 40 videos at WindowsClient.NET including some good ones on Styles.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes\InfoTextBox.generic.xaml"/>
<ResourceDictionary Source="Themes\TextBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<Style x:Key="PathMargin" TargetType="{x:Type Path}">
<Setter Property="Margin" Value="0,8,0,32"/>
</Style>
</ResourceDictionary>
</Application.Resources>
I did themes for like items that made "sense" to group together. But for my paths I just wanted to make sure the margins were the same, so they just got a Style entry.
Hope this helps.
David
At http://www.xamltemplates.net you cand download a free style for all the controls in wpf and you can have a play with it.
The coolest thing about resources is that you can index them by name (x:Key) or by type (TargetType), or by both. If you index them by x:Key, you must always refer to them as a static resource {StaticResource MyKey}. If you index them by type, they will be applied to every instance of that type
That means if you have a button, you can put a control template indexed on the type "Button" in your application resources and have every button automatically pick up that style in any window in your app without any further work on your part.
TargetType doesn't have to be a CLR type; it can also be the name of an element in an embedded XML "island". Index your datatemplate on the element name and that template will be used wherever you bind against that x:Data XML island. I found this particularly useful when adding a hybrid help/input system into a small application I'm working on. I could add an xml document as a data island in my application resources that had not only the input values, but also a name, description, and tooltip attributes for each option. I then created data templates for each node type and bound the island to a treeview. Without any codebehind or custom controls (!!!!), I got a very rich UI experience that was also very DRY. Imagine doing something like that in windows forms in anything less than a week. I did it in a single evening, and I'm pretty new to WPF.
I so friggen love WPF.
Comments are closed.
It looks like doing ASP.NET but that looks like Forms and have way more power.
Hummm... this weekend!