What is the easiest way to set spacing between items in StackPanel or any other Panel?
Seems like an easy question, but most of the time it requires just too much xaml work.
If we’re just putting items of the same type, we can use “anonymous” styles, like so:
1: <StackPanel>
2: <StackPanel.Resources>
3: <Style TargetType="Button">
4: <Setter Property="Margin" Value="5" />
5: </Style>
6: </StackPanel.Resources>
7: <Button Content="Hello" />
8: <Button Content="Hello" />
9: <Button Content="Hello" />
10: </StackPanel>
Due note, that even for this basic situation we have to resort to a LOT of code. Many times, XAML is just too damn verbose. I don’t enjoy writing 5 (!) lines of code for something this trivial, especially compared to how elegant HTML & CSS can be in this situation.
To make things worse, this is not even the worst case… what if I have different controls inside the StackPanel?
The first intuition of most programmers is to target a base class that everybody shares, like so:
1: <StackPanel>
2: <StackPanel.Resources>
3: <Style TargetType="Control">
4: <Setter Property="Margin" Value="5" />
5: </Style>
6: </StackPanel.Resources>
7: <TextBox Text="Hello" />
8: <Button Content="Hello" />
9: <Button Content="Hello" />
10: </StackPanel>
Although this intuition makes sense, this simply doesn’t work (because of DependecyProperties Value Resolution. Default value of a class has higher precedence than a Style).
Seemingly, we’re stuck. And when we’re stuck in WPF there’s almost always a cool solution – Attached Behaviors!
MarginSetter Attached Property
An Attached Property (or Behavior) can come very handy in this situation. All we need to do is to create an attached property that can be attached to anything that inherits from Panel, and that iterate over its children and set a margin for them:
1: public class MarginSetter
2: {
3: public static Thickness GetMargin(DependencyObject obj)
4: {
5: return (Thickness)obj.GetValue(MarginProperty);
6: }
7:
8: public static void SetMargin(DependencyObject obj, Thickness value)
9: {
10: obj.SetValue(MarginProperty, value);
11: }
12:
13: // Using a DependencyProperty as the backing store for Margin. This enables animation, styling, binding, etc...
14: public static readonly DependencyProperty MarginProperty =
15: DependencyProperty.RegisterAttached("Margin", typeof(Thickness), typeof(MarginSetter), new UIPropertyMetadata(new Thickness(), MarginChangedCallback));
16:
17: public static void MarginChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
18: {
19: // Make sure this is put on a panel
20: var panel = sender as Panel;
21:
22: if (panel == null) return;
23:
24:
25: panel.Loaded += new RoutedEventHandler(panel_Loaded);
26:
27: }
28:
29: static void panel_Loaded(object sender, RoutedEventArgs e)
30: {
31: var panel = sender as Panel;
32:
33: // Go over the children and set margin for them:
34: foreach (var child in panel.Children)
35: {
36: var fe = child as FrameworkElement;
37:
38: if (fe == null) continue;
39:
40: fe.Margin = MarginSetter.GetMargin(panel);
41: }
42: }
43:
44:
45: }
The beauty of Attached Properties is once you write an added behavior in this fashion, it’s fully encapsulated, and you can use it extremely easy. To use MarginSetter, you would use the following syntax: local:MarginSetter.Margin="5"
1: <StackPanel local:MarginSetter.Margin="5">
2: <TextBox Text="hello" />
3: <Button Content="hello" />
4: <Button Content="hello" />
5: </StackPanel>
The result is just what we wanted – every child of the StackPanel now has a Margin of 5 !
