WPF Button with image + text and change the image in codebehind

I'm trying to create a button which contains an Image and a Text. But, the image can be changed by code behind at runtime. My images are png, and everyone is in my ResourceDictionary as:

<BitmapImage x:Key="iconLogIn" UriSource="/Images/Icons/lock.png" />

So, I've started with this style, with no Template or ContentTemplate.

<Style x:Key="MyTestButton" TargetType="{x:Type Button}">
    <Setter Property="Height" Value="80" />
    <Setter Property="Width" Value="100" />
</Style>

And my XAML is:

<Button x:Name="cmdOption1" Style="{StaticResource MyTestButton}" Margin="8" Click="cmdOption1_Click">
    <Image Source="{DynamicResource iconLogIn}" />
</Button>

Then, my code behind to change image is:

cmdOption1.Content = new Image() { Source = ((BitmapImage)FindResource("iconLogOut")) };

So far, this works.

But, only holds the image, I want to place a text under the image.

So I've read this post, and HighCore's answer, option #2, might fulfill my requirement. But, now comes a new problem:

First, this is the new style, with a simple template

<Style x:Key="MyTestButton" TargetType="{x:Type Button}">
    <Setter Property="Background" Value="#EEEEEE" />
    <Setter Property="Foreground" Value="DarkSlateGray" />
    <Setter Property="Height" Value="80" />
    <Setter Property="Width" Value="100" />
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Padding" Value="2"/>
    <Setter Property="FontSize" Value="12" />
    <Setter Property="FontWeight" Value="SemiBold" />
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="BorderBrush" Value="DarkGray" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border>
                    <StackPanel>
                        <Image Source="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" Height="50" Width="50"/>
                        <ContentPresenter Grid.Row="0" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
                    </StackPanel>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter Property="Foreground" Value="DarkOrange" />
                        <Setter Property="OpacityMask" Value="#AA888888"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="Gray" />
                        <Setter Property="BorderBrush" Value="DarkGray" />
                        <Setter Property="Background" Value="White" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
</Style>

My new XAML is:

<Button x:Name="cmdOption1" Tag="{StaticResource iconLogIn}" Content="LOG IN" Style="{StaticResource MyTestButton}" Margin="8" Click="cmdOption1_Click" />

*Also with Tag="{DynamicResource iconLogIn}"

And my code behind to change the image and text:

cmdOption1.Tag = new Image() { Source = ((BitmapImage)FindResource("iconLogOut")) };
cmdOption1.Content = "LOG OUT";

With this, the content text changes from "LOG IN" to "LOG OUT". But the image is not shown anymore, just nothing in the place of the image, and no error or exception is thrown.

I'd like to know whats the solution, and whats happening?, why the image is just not changing, but disappeared?

Thanks in advance.

Answers


You create a new Image control and assign it to the Button's Tag property. Hence the Image control in the ControlTemplate gets its Source property set to another Image control. That won't work. Just assign the BitmapImage from the Resources to Tag.

Instead of

cmdOption1.Tag = new Image() { Source = ((BitmapImage)FindResource("iconLogOut")) };

you should simply write this:

cmdOption1.Tag = (BitmapImage)FindResource("iconLogOut");

I think that you should consider a cleaner approach, by using MVVM, with databinding and implementation of the INotifyPropertyChanged.

At first: Use a ViewModel in which you define the properties which define your ImageUri and Text:

    private Uri _myImageUriProperty;
    public Uri MyImageUriProperty
    {
        get { return _myImageUriProperty; }
        set { _myImageUriProperty = value; RaisePropertyChanged(() => MyImageUriProperty); }
    }

    private string _myTextBlockProperty;
    public string MyTextBlockProperty
    {
        get { return _myTextBlockProperty; }
        set { _myTextBlockProperty = value; RaisePropertyChanged(() => MyTextBlockProperty); }
    }

Furthermore: Inside your button you can use a placeholder to put multiple UI Elements within your button. And bind them to your properties from your viewmodel:

    <Button x:Name="MyButton"
            Click="MyButton_OnClick">
        <StackPanel>
            <Image x:Name="MyImage"
                   Source="{Binding MyImageUriProperty}" />
            <TextBlock x:Name="MyTextBlock"
                       Text="{Binding MyTextBlockProperty}" />
        </StackPanel>
    </Button>

Finally: Use the click event in the codebehind to reset the values in your viewmodel (which you have set in the constructor of your codebehind as being the DataContext. The PropertyChanged event will trigger the update in the UI.

    private void MyButton_OnClick(object sender, RoutedEventArgs e)
    {
        ViewModel.MyImageUriProperty = new Uri("mynewuri.png", UriKind.Relative)
        ViewModel.MyTextBlockProperty = "LogOut";
    }

Your XAML works fine. The only thing in the code necessary to set an Image in this way:

private void cmdOption1_Click(object sender, RoutedEventArgs e)
{
    BitmapImage MyBitmapImage = ((BitmapImage)FindResource("iconLogOut"));
    cmdOption1.Tag = MyBitmapImage;

    cmdOption1.Content = "LOG OUT";
}

As an alternative (just for comparison methods), you can use this trick without using Tag via the function FindChild<>. Your part of template:

<ControlTemplate TargetType="{x:Type Button}">
    <Border>
        <StackPanel>
            <Image x:Name="MyContentImage" Source="{StaticResource iconLogIn}" Height="50" Width="50" />
            <ContentPresenter Grid.Row="0" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
        </StackPanel>
    </Border>

    ...

Code behind:

private void cmdOption1_Click(object sender, RoutedEventArgs e)
{
    // Find the Image in template
    Image MyContentImage = FindChild<Image>(cmdOption1, "MyContentImage");
    MyContentImage.Source = ((BitmapImage)FindResource("iconLogOut")); 

    cmdOption1.Content = "LOG OUT";
}

Listing of FindChild<>:

    public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
    {
        if (parent == null)
        {
            return null;
        }

        T foundChild = null;

        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);

        for (int i = 0; i < childrenCount; i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            T childType = child as T;

            if (childType == null)
            {
                foundChild = FindChild<T>(child, childName);

                if (foundChild != null) break;
            }
            else
                if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;

                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        foundChild = (T)child;
                        break;
                    }
                    else
                    {
                        foundChild = FindChild<T>(child, childName);

                        if (foundChild != null)
                        {
                            break;
                        }
                    }
                }
                else
                {
                    foundChild = (T)child;
                    break;
                }
        }

        return foundChild;
    }   

Need Your Help

How to insert couchdb documents in bulk?

javascript node.js rest couchdb bulkinsert

I'm tasked with modifying a legacy app so that users can upload payroll adjustments in bulk. Currently they have to fill out a form and input the data item by item, hitting submit after each one. I'm

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.