Solution: Styling a large amount of controls

December 11, 2008

I’m really on this “Attached Property” kick. I’m probably totally over using them, but I think it’s such a powerful tool when used in the right scenario. I’m not really sure what the right scenario actually is, but I keep running into situations where to me they just make sense. Also there’s something about adding behavior to objects without actually inheriting from them that gives me a sense that I’m doing something really cool, cutting edge, and adventerous…alright that’s a little ridiculous. Moving on…

So I had posted a question on StackOverflow (by the way this is such an awesome site) and also wrote here about finding the best approach to applying styles to a large amount of controls. I spent an hour or so on the phone with Karl Shifflett discussing the pro’s and con’s of the various approaches to take which I wont re-hash here. I finally landed on this idea that uses my good buddy DependencyProperty.RegisterAttached.

Code
What I’ve done is create 4 styles in a resource dictionary

  1. A style for the containing panel (i.e. Grid, StackPanel, etc.)
  2. A style for the first item(s)
  3. A style for the last items(s)
  4. A style for all the items in between

styles121008

The reason I create different styles is that depending on where the object is in the panel I may want to apply more or less margin, or to different amounts on different sides. Obviously this is just one particular scenario, but you get the point. Then I set the the attached property on the containing grid like this (can also be a StackPanel/DockPanel):

<Grid local:AttachedProperties.IsStandardContainer="True">

Essentially what the attached property does is iterates through all the children in the Panel and applies one of the specified styles to each element. Care is taken not to override any explicit styles set on any child objects.

Here is a full example including the code for the attached property. Make sure you change the extension from .doc to .zip.

kick it on DotNetKicks.com

Advertisements

Joel on Software

December 10, 2008

Trying to find something interesting to read other than the typical MSDN mag or a 700+ page manual on the latest Microsoft development platform, I hit my co-worker up for his copy of “Joel on Software” (the first one). I’m only about half-way through it, and it has been incredibly eye-openning and inspiring. I’ve never read any “technical” book other than the how-to or programmer-to-programmer types so it’s been quite a different experience.

There are two main points that really stick out for me in this book:

  1. Write specs for everything you have to code (that means before you write the code)
  2. If you are bad at writing, learn to write

I think the biggest reason this really sticks out to me is that I hate writing. No…I loathe writing; and according to Joel one of the key components of running successfull software projects and even being a successfull developer is being able to write specs.

So how do I become a good writer when I hate writing? He suggests writing an essay everyday. It can be on anything. So, that’s what I’m setting out to do. I’m going to try writing an essay every day on something programming related. I’m hoping to cover everything from coding tips to life experiences learned in the world of coding. Hopefully it will benefit somebody out there, but most importantly, hopefully I’ll become a better writer, developer, and project manager.


CommandBindings in MVVM

December 10, 2008

Anyone who has tried to implement RoutedCommands in WPF using M-V-VM has undoubtedly run into issues. Commands (non-UI commands that is) should be implemented in the ViewModel. For instance if I needed to save a CustomerViewModel then I would implement that as a command directly on my CustomerViewModel. However if I wanted to pop up a window to show the users addresses I would implement a ShowCustomerAddress command directly in the view since this a UI specific function.

The problem lies in the way that RoutedCommands find their associated CommandBindings. In order for a command to be executed it has to have a CommandBinding which tells it how to handle the Executed and CanExecute events. When you associate a control with a command it walks up the logical tree to find the first command binding associated with it. A typical scenario would look like this:

<UserControl>
<UserControl.CommandBindings>
<CommandBinding Command="ApplicationCommands.Save" CanExecute="Save_CanExecute" Executed="Save_Executed" />
</UserControl.CommandBindings>

<Button Command=”ApplicationCommands.Save” Content=”Save” />
</UserControl>

This approach forces us to define the behavior of of the command directly in our view, when really that logic should be in the ViewModel. So how do we get the the View to look for the CommandBindings in the ViewModel instead of the baked in behavior of searching up the logical tree? Well, we can’t. So instead, we need a way to get the CommandBindings out of the ViewModel and into the View. The first thing we need is a CommandBindingCollection property in our ViewModel:

private readonly CommandBindingCollection _CommandBindings;
public CommandBindingCollection CommandBindings
{
   get
   {
      return _CommandBindings;
   }
}

Now we create our CommandBindings and add them to the CommandBinding property we created:

public CustomerViewModel(Customer model)
{
//Create a command binding for the Save command
CommandBinding saveBinding = new CommandBinding(ApplicationCommands.Save, SaveExecuted, SaveCanExecute);

//Register the binding to the class
CommandManager.RegisterClassCommandBinding(typeof(CustomerViewModel), saveBinding);

//Adds the binding to the CommandBindingCollection
CommandBindings.Add(saveBinding);
}

Now with a little Attached Property goodness we add the commandbindings of our ViewModel to our View:

public static DependencyProperty RegisterCommandBindingsProperty =  DependencyProperty.RegisterAttached("RegisterCommandBindings", typeof(CommandBindingCollection), typeof(AttachedProperties), new PropertyMetadata(null, OnRegisterCommandBindingChanged));

public static void SetRegisterCommandBindings(UIElement element, CommandBindingCollection value)
{
if(element != null)
element.SetValue(RegisterCommandBindingsProperty, value);
}
public static CommandBindingCollection GetRegisterCommandBindings(UIElement element)
{
return (element != null ? (CommandBindingCollection)element.GetValue(RegisterCommandBindingsProperty) : null);
}
private static void OnRegisterCommandBindingChanged
(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UIElement element = sender as UIElement;
if (element != null)
{
CommandBindingCollection bindings = e.NewValue as CommandBindingCollection;
if (bindings != null)
{
element.CommandBindings.AddRange(bindings);
}
}
}

This is how you would use it in the view:

<UserControl local:AttachedProperties.
RegisterCommandBindings="Binding CommandBindings}" >

image

Conclusion

There are few other examples of how to do this like Josh Smith posted on CodePlex, but I think this is a much more straightforward approach and requires less code. I really hope this helps someone dealing with the same issue. If you think there is a better way, please let me know.

Code

There is a full example that you can download here. Be sure to change the file extension from .doc to .zip.
kick it on DotNetKicks.com


Finding the Best Approach to Styling a LOB App

December 4, 2008

In my LOB apps I usually wind up with containers that contain a bunch of different TextBlocks and TextBoxes for users to enter data. Normally I need to apply a certain margin or vertical/horizontal alignment to each control.

Let’s say I have Grid on my form that looks like this (a lot of markup was eliminated for brevity):

<Grid>
   <Grid.ColumnDefinitions.../>
   <Grid.RowDefinitions.../>
   <TextBlock Text="MyLabel" />
   <TextBox Text={Binding ...}/>
   .
   .
   <!-- Repated a bunch more times along with all of the Grid.Row, 
Grid.Column definitions -->
   </Grid>

Now let’s say I need every single item contained in my grid to have Margin=”3,1″ VerticalContentAlignment=”Left” VerticalAlignment=”Center”. There are several ways to achieve this:

  1. Set the properties directly on each control – BAD!! Does not allow for skinning or centralizing styles.
  2. Create a Style with an x:Key=”MyStyleName” and apply the style to each control. Better…Makes centralizing styles and skinning more manageable but still requires a ton of markup, and can become unwieldy.
  3. Create a global style (i.e. don’t specify an x:Key and set the TargetType={x:Type TextBox/TextBlock} – BAD!! The problem with this is that it affects ALL controls in the app that don’t explicity override this style. This can be bad for things like menus, grids, and other controls that use textblocks and textboxes.
  4. Create a style that targets the Grid and explicitely sets the dependecy propety values like <Setter Property="Frameworkelement.Margin" Value="3,1" /> Not bad…it correctly applies the style to every element in it’s content, but also applies it directly to the Grid itself…not exactly what I want.

So what approach do you take and why? What works the best?

kick it on DotNetKicks.com