3

I'm trying to create a <Button/> with a png image for the background AND a background color.

Note: The png image source may not yet be known, so I can't just put them in the template.

I've got a Style that looks like this:

<Style x:Key="MyButton" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border CornerRadius="0">
                    <Image Margin="0" Source="/MyApp;component/Images/my-image.png" Stretch="None" />
                    <Border.Style>
                        <Style TargetType="{x:Type Border}">
                            <Setter Property="Background" Value="LimeGreen" />
                        </Style>
                    </Border.Style>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

which gives me a button with a png image on top of a solid LimeGreen background.

My MainWindow.xaml looks somewhat like this:

<Button Style="{StaticResource MyButton}" Click="btnMine_Click" /* some other props */ />

and the code behind it is like this:

private void btnMine_Click(object sender, RoutedEventArgs e)
{
    // TODO: change the image src on this button
    var myButton = sender as Button;

    // This won't work:
    // myButton.Border.Image.Source = getNewImageString();
}

How do I change the image source from /MyApp2;component/Images/my-image.png to something else?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
David Murdoch
  • 87,823
  • 39
  • 148
  • 191
  • *p.s. manually editing a .xaml file is **not** programmatic, there seem to be several answers here on SO that seem to think so.* :-/ – David Murdoch Apr 04 '13 at 19:14
  • do you want it exactly like that (your click handler)? And can you change the XAML at all? Making it 'all programmatic' only makes sense if you're unable to 'touch' that Style. – NSGaga-mostly-inactive Apr 04 '13 at 19:25
  • Sure, I *can* edit the Style, but there are just too many buttons for that to be realistic. All the buttons, however, share the same background color(s). – David Murdoch Apr 04 '13 at 19:30
  • And no, not *exactly* like that, its just an example. What I want to do is to take a `button` Object and change its background image after certain events (click, mouseenter, window resize, etc). Make sense? – David Murdoch Apr 04 '13 at 19:32
  • add click trigger event to button and change background in template ` ` – KF2 Apr 04 '13 at 19:32
  • @irsog, this is exactly what I want to do... but I can't figure out how. – David Murdoch Apr 04 '13 at 19:36
  • You need to `Bind` not 'look for' controls. Bind the Button to some ButtonInfo model - which has the Image. Then in Style Bind to that Property - instead of hardcoded path. – NSGaga-mostly-inactive Apr 04 '13 at 19:38
  • what dose exactly getNewImageString(); return? – KF2 Apr 04 '13 at 20:00

2 Answers2

2

Sigh, in Windows Forms it was literally one line. In WPF it looks like it will be several hundred.

That's because it was the ONLY thing you could place in a button in winforms. WPF is a true UI framework, not some random semi-deprecated dinosaur that only allows to do the default stuff (which by the way looks horrible).

Option 1: Place the Image as Button's Content:

If you just want to place an Image in the button, and nothing else, why not just do that?

        <Style TargetType="Button">
            <Setter Property="Background" Value="LimeGreen"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}">
                            <ContentPresenter ContentSource="Content"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Then:

     <Button>
        <!-- Background not set, defaults to what's set in the Style -->
        <Image Source="./ChessPieces/BlackKnight.png"/>
    </Button>
    <Button Background="Red">
        <Image Source="./ChessPieces/BlackBishop.png"/>
    </Button>
    <Button Background="Blue">
        <Image Source="./ChessPieces/BlackPawn.png"/>
    </Button>

    <!-- WPF's idea of "Dynamic" is not the same as win(hack)forms' -->
    <Button Background="White">
        <Image Source="{Binding SomeStringPropertyDefinedInAViewModel}"/>
    </Button>

Result:

enter image description here

Option 2 (not very elegant): Use the Tag Property:

If, in addition to the Image, you want to put something else inside the Button, you can resort to a somewhat hacky approach of putting the ImageSource in the Button's Tag property.

        <Style TargetType="Button">
            <Setter Property="Background" Value="LimeGreen"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}">
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"
                                       Height="50" Width="50"/>
                                <ContentPresenter ContentSource="Content"/>
                            </StackPanel>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Then:

    <Button Tag="./ChessPieces/BlackKnight.png"
            Background="Blue"
            Foreground="White"
            Content="Some Text"/>

    <Button Tag="./ChessPieces/BlackBishop.png"
            Background="LightGray">
        <CheckBox Content="A CheckBox!" VerticalAlignment="Center"/>
    </Button>

Result:

enter image description here

Option 3: Use an Attached Property

Same as option 2, but using a property declared elsewhere, for example:

 <Button ButtonImage.Source="./ChessPieces/BlackBishop.png"
         <!-- etc -->

Option 4: Create a Custom Control:

Create a class (.cs file with no XAML) derived from Button and add a DependencyProperty to hold the image, then set the template to that and use that value.

Option 5: MVVM

Create a ButtonViewModel, or actually use a DelegateCommand declared in the ViewModel to bind the Button's properties.

Not an Option: Traverse the Visual Tree and change the Image.Source in code.

That's not something you will want to do. It's not a good approach at all.

I could go on forever, but I have to go to sleep. If you want me to elaborate on any of these approaches just let me know.

Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
0

A few ways spring to mind:

  1. Access the style at runtime and traverse it to find the image
  2. Give the image a name and reference that at runtime and just change the source

The best solution I believe is to create a usercontrol and have the image source as a dependency property see: http://www.codeproject.com/Articles/224230/Exploring-the-use-of-Dependency-Properties-in-User

That way you'll have a ready-to-use "custom" control that supports what you're after and allows you to just reference it directly and use bindings, that way you avoid the messy solutions from using a style.


After discussion and expansion on requirements, please see:

How can I access ResourceDictionary in wpf from C# code?

Community
  • 1
  • 1
Clint
  • 6,133
  • 2
  • 27
  • 48
  • `Access the style at runtime and traverse it to find the image` that is essentially what I want to do, but I can't figure out how. – David Murdoch Apr 04 '13 at 19:21
  • @DavidMurdoch where is the style defined? That will completely influence how you access it at runtime, traversal would be trickier than simply creating a usercontrol. – Clint Apr 04 '13 at 19:29
  • It's in a file named "Templates.xaml" (a ResourceDictionary), which is referenced in my `App.xaml`. – David Murdoch Apr 04 '13 at 19:33
  • I may have misunderstood you, I don't think I want to access the `Style` itself, because it will be applied to many buttons. I just want to *override* the style's "properties" (or whatever they'd be called in xaml) on specific buttons. – David Murdoch Apr 04 '13 at 19:35
  • @DavidMurdoch this isn't possible afaik, the only way of having button-specific overrides would be by using a UserControl. – Clint Apr 04 '13 at 19:35
  • Sigh, in Windows Forms it was literally **one line**. In WPF it looks like it will be several hundred. – David Murdoch Apr 04 '13 at 19:37
  • @DavidMurdoch it can seem that way sometimes in WPF, it's mostly because WPF is a blessing and a burden, it's so extensible, a UserControl wouldn't require much more work and would be a useful asset for you to have. – Clint Apr 04 '13 at 19:40
  • Is there any reason you aren't binding to a 'Icon' property on the button model or some such thing? You **can** just put them in the template if they are bound. – Charleh Apr 04 '13 at 19:41
  • I can't find an `Icon` property on the button? – David Murdoch Apr 04 '13 at 19:44
  • @DavidMurdoch he's on about using a model to bind to, he's not on-topic for your question. – Clint Apr 04 '13 at 19:45