tags:

views:

37

answers:

2

I imagine you can draw prety much anything with enough XAML experience, but i'm a C++ guy new to WPF. I need a button that has a gradient color plus a special border around it. So what do us C++ guys do? We go to the designer, ask for a Left vertical image, Middle one and Right one, and then overriding the Win32 drawing messages practically draw the button ourselves.

What is the WPF way of doing this? Do I need to master XAML for this, or can I somehow make use of those 3 images to make up a button? My naive thought was doing something like this, were i divide the button in 3 parts, each having a different background stretched across, but that didn't give me the results I want. Can I have control of how a button is drawn?

<Style x:Key="NavButtonStyle" TargetType="{x:Type Button}">
        <Setter Property="OverridesDefaultStyle" Value="True" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid ShowGridLines="False">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <Grid Grid.Column="0">
                            <Grid.Background>
                                <ImageBrush ImageSource="/Images/left.png"/>
                            </Grid.Background>
                        </Grid>
                        <StackPanel Grid.Column="1" HorizontalAlignment="Center" Orientation="Horizontal">
                            <StackPanel.Background>
                                <ImageBrush Stretch="Fill" ImageSource="/Images/middle.png"/>
                            </StackPanel.Background>
                            <Image Source="/Images/Icons/myIcon.png"/>
                            <TextBlock Text="myCaption"/>
                        </StackPanel>
                        <Grid Grid.Column="2">
                            <Grid.Background>
                                <ImageBrush Stretch="Fill" ImageSource="/Images/right.png"/>
                            </Grid.Background>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

The other problem is how to make this a generic template so that I can duplicate the look and feel but just change the icon and text. I suppose the right way of doing this task is come up with a CustomControl class inheriting from Button?

A: 

1#: I don't know what do you mean by getting control over button? But we have control to build your button style as you like. In the above XAML i have seen only the Template, but there is no triggers or VisualStates to mention the different states/behaviours/effects of the button like Mouse over, Mouse leave, Button Press etc.. Since this control overrides the default style, these effects will behaves as default button.

2#: Since this is a custom control, you can add the styles under "Themes" folder & generic.xaml. You should not specify the Key name, then it will be apply to all the customcontrol buttons as below:

<Style TargetType="{x:Type custom:CustomerControlType}"> 
Ragunathan
A: 

The really short answer is that this is what you use Expression Blend for.

The slightly less short answer is that most of what you describe can be accomplished by editing the control template, if you understand how control templates work and you know what to edit. Expression Blend is a good tool for doing this because Blend makes editing a control's template relatively easy, and because Blend remembers a lot of thing that you're likely to forget, particularly if you're relatively new to WPF.

For instance, if I go into Blend, draw a button on the artboard, and edit a copy of its control template, here's what I get before I even start messing with anything:

<Style x:Key="ButtonFocusVisual">
  <Setter Property="Control.Template">
    <Setter.Value>
      <ControlTemplate>
        <Rectangle Stroke="Black" StrokeDashArray="1 2" StrokeThickness="1" Margin="2" SnapsToDevicePixels="true"/>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
  <GradientStop Color="#F3F3F3" Offset="0"/>
  <GradientStop Color="#EBEBEB" Offset="0.5"/>
  <GradientStop Color="#DDDDDD" Offset="0.5"/>
  <GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
<Style x:Key="ButtonControlTemplate" TargetType="{x:Type Button}">
  <Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
  <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
  <Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
  <Setter Property="BorderThickness" Value="1"/>
  <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
  <Setter Property="HorizontalContentAlignment" Value="Center"/>
  <Setter Property="VerticalContentAlignment" Value="Center"/>
  <Setter Property="Padding" Value="1"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Button}">
        <Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" RenderDefaulted="{TemplateBinding IsDefaulted}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}">
          <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" RecognizesAccessKey="True"/>
        </Microsoft_Windows_Themes:ButtonChrome>
        <ControlTemplate.Triggers>
          <Trigger Property="IsKeyboardFocused" Value="true">
            <Setter Property="RenderDefaulted" TargetName="Chrome" Value="true"/>
          </Trigger>
          <Trigger Property="ToggleButton.IsChecked" Value="true">
            <Setter Property="RenderPressed" TargetName="Chrome" Value="true"/>
          </Trigger>
          <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="#ADADAD"/>
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

In principle, all you need to do is find the ContentPresenter element (which is where the button's content gets inserted) and replace what surrounds it with your own nifty thing. You can copy that XAML into your own project and mess around with it and you'll get something that sort of works.

But: There's a lot of stuff in there that you need to understand before you'll end up producing a button that actually does what you want. Most of it represents functionality and behavior that, if you're anything like me when you have the bright idea to develop your own button, you haven't troubled yourself to think about. Like:

  • How should the button's appearance change when it has the focus?
  • How should the button's appearance change when it's disabled?
  • What events should change the button's visual appearance?

You'll note, if you look at this template, that a lot of the answers to that last question are embedded in the Microsoft_Windows_Themes:ButtonChrome element. All the stuff it does in code (e.g. RenderPressed) is stuff you have to build into your XAML via triggers. (Or you can write your own button-chrome-like object.)

I guess what I'm saying is that you're not on the wrong track, but there's a lot you will need to learn.

As far as your second question goes, yes, you can create a UserControl and that exposes properties for the caption and image. You might also experiment a little with the fact that the button is actually a content control, e.g.:

<Button Height=50>
  <DockPanel>
    <Image Width="32" Source="image.png"/>
    <TextBlock Text="My button's caption" TextWrapping="Wrap" VerticalAlignment="Center"/>
  </DockPanel>
</Button>
Robert Rossney
Thanks Robert for your detailed answer - it does help.