tags:

views:

190

answers:

4

Consider the follow 2 XAML snippets (which are side-by-side in the file):

<Button
    x:Name="BuyButton"
    Margin="0,0,1,1"
    IsEnabled="{Binding CanBuy}"
    >
    <StackPanel
     DataContext="{Binding Product}">
     <TextBlock
      Foreground="Red"
      Text="BUY" />
     <TextBlock
      Foreground="Red"
      Text="{Binding BuyPrice}" />
    </StackPanel>
    </Button>

<Button
    x:Name="SellButton"
    Margin="0,0,1,1"
    IsEnabled="{Binding CanSell}"
    >
    <StackPanel
     DataContext="{Binding Product}">
     <TextBlock
      Foreground="Red"
      Text="SELL" />
     <TextBlock
      Foreground="Red"
      Text="{Binding SellPrice}" />
    </StackPanel>
</Button>

How does one remove duplication in WPF? I have approx 4 uses (2 shown here) of this type of button, and they're 80% the same, if not more. I could extract this into a user control and put a few DPs on it and then I'd have one control, but I fear that I will start littering my code base with tons of user controls (I have a lot of "one-off" situations like this). I don't like the DataTemplate solution here because I'd still need two templates, which would have repeated code. Without creating a bunch of templates/controls, is there a way of making this code follow DRY?

+2  A: 

I don't have an alternative solution but I disagree on your argument of one-off controls. In my opinion it boils down to choosing between a lot of duplicate code or less duplication with a number of control that are quite specific. If this were plain code you would probably not think twice about performing an extract method when confronted with several chunks of identical code, why is the fact that this code is now XAML make your decision any different?

Bas Bossink
Off the cuff I can quickly say because I don't have to create a new file with extract method. But yes, I understand your argument, was having a similar internal dialogue.
DavidN
A: 

Separate it into a user control. DRY principles and refactoring should be treated the same across just about any platform.

Soviut
+2  A: 

If your goal is just to reduce repetition, you can eliminate a lot of it by using Styles and setting common attached properties on a parent element:

<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Margin" Value="0,0,1,1"/>
        </Style>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="Foreground" Value="Red"/>
            <Setter Property="DataContext" Value="{Binding Product}"/>
        </Style>
    </StackPanel.Resources>
    <Button x:Name="BuyButton" IsEnabled="{Binding CanBuy}">
        <StackPanel>
            <TextBlock Text="BUY"/>
            <TextBlock Text="{Binding BuyPrice}"/>
        </StackPanel>
    </Button>
    <Button x:Name="SellButton" IsEnabled="{Binding CanSell}">
        <StackPanel>
            <TextBlock Text="SELL"/>
            <TextBlock Text="{Binding SellPrice}"/>
        </StackPanel>
    </Button>
</StackPanel>

Note that this ends up being more code... which is why "3 strikes, then refactor" is the rule of thumb.

Robert Macnee
+3  A: 

A ControlTemplate might work:

<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="{x:Type Button}">
<StackPanel Height="Auto" Width="Auto">
 <TextBlock Text="{TemplateBinding Content}" Foreground="Red"/>
 <TextBlock Text="{TemplateBinding Tag}" Foreground="Red"/>
</StackPanel></ControlTemplate>

I used TemplateBinding to get the two changeable pieces of data. Now when you create the button, apply the Template, and set the Bindings to your elements:

<Button x:Name="BuyButton"
    Margin="0,0,1,1"
    IsEnabled="{Binding CanBuy}"
    Template="{DynamicResource ButtonControlTemplate1}"
    Content="Button" 
    Tag="{Binding BuyPrice}"/>

The only thing missing is the DataContext: just set it at a container above the two buttons.

I haven't tried it specifically, but it seems it should work. I chose "Tag" above because I needed a second element for Binding. I'd love to see different suggestions about this.

You probably also want to break the ForegroundColor="Red" type stuff into a style.

Joel Cochran
For 2 elements, yes your choice of "Tag" will work. With 3 elements, now we're back to square one (I don't think a MultiBinding on Tag would be desireable ;)). Any other suggestions on that specific issue?
DavidN
Unfortunately, I do not, which is why I was hoping to see some more comments about this approach. I wonder if you could put another ControlTemplate inside the Button Content, and then navigate it in the TemplateBinding somehow?
Joel Cochran