locked
image button

    Question

  •  Hey guys,

    I recently started to work in WPF, and I wanted to create an image button control. What I wanted this control to do is to have an initial image and for it to change to a second image on mousedown, just like the image buttons we see on websites done with javascript.

    So I created a XAML user control and this is the code:

    <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown" MouseLeftButtonUp="Grid_MouseLeftButtonUp" MouseEnter="Grid_MouseEnter" MouseLeave="Grid_MouseLeave">
    <Grid.Background>
    <ImageBrush Stretch="Fill" x:Name="imButtonBckgnd"></ImageBrush>
    </Grid.Background>
    </Grid>

    In the code behind I set the original image for the imButtonBckgnd and on the mouse button down and up events I change this Image.

    I added this user control to my main Window and it's all working great, but for some reason when I deploy the project to another machine it takes like 20 to 40 seconds to load the main window after that it all runs smoothly. I know this long load time is caused by this control in particular because when I remove it I dont have the problem anymore and I also have other user controls in my window which do not cause this problem.

    So my question is, is this the best way to accomplish what I'm trying to do or am I doing something wrong. BTW I did try to use a button but I couldn't get rid of the border of the button, I don't want it to appear all I want to be seen is my images.

    Any suggestions would be helpful.

    Thanks in advance.

    Mike

    Mike
    Tuesday, June 03, 2008 1:37 PM

Answers

  • OK this gets a little more complicated - taking the "no code approach" - the best way to do this is to use a control that derives from Button and with a custom control template.

    1. In VS add a new WPF custom control "Custom Control (WPF)" - this will add CustomControl1.cs and Themes\Generic.xaml

    2. Rename CustomControl1.cs to ImageButton.cs

    3. In WPF if you want to be able to bind, use animations etc. you have to create DependencyProperties instead of ordinary CLR properties. So let's go ahead and do that for your 2 properties. Also we want our own control template not the default one Button gets so we'll override that too using DefaultStyleKeyProperty - giving this:

        public class ImageButton : Control  
        {  
            public static readonly DependencyProperty MouseOverImageProperty;  
            public static readonly DependencyProperty OriginalImageProperty;  
     
            static ImageButton() {  
                    DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));  
                    MouseOverImageProperty = DependencyProperty.Register("MouseOverImage"typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));  
                    OriginalImageProperty = DependencyProperty.Register("OriginalImage"typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));  
            }  
     
            public ImageSource MouseOverImage {  
                get { return (ImageSource)GetValue(MouseOverImageProperty); }  
                set { SetValue(MouseOverImageProperty, value); }  
            }  
     
            public ImageSource OriginalImage {  
                get { return (ImageSource)GetValue(OriginalImageProperty); }  
                set { SetValue(OriginalImageProperty, value); }  
            }  
     
        }  
     

    4. Now you can go the generic.xaml and add a default template for your control:

    <ResourceDictionary  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApplication65">  
    <!-- REMEMBER TO CHANGE WpfApplication65 TO THE RIGHT VALUE -->
     
     
        <Style TargetType="{x:Type local:ImageButton}">  
            <Setter Property="Template">  
                <Setter.Value> 
                    <ControlTemplate TargetType="{x:Type local:ImageButton}">  
                        <Image x:Name="Image1" Source="{TemplateBinding OriginalImage}"/>  
                        <ControlTemplate.Triggers> 
                            <Trigger Property="IsMouseOver" Value="True">  
                                <Setter TargetName="Image1" Property="Source" Value="{Binding Path=MouseOverImage, RelativeSource={RelativeSource TemplatedParent}}"/>  
                            </Trigger> 
                        </ControlTemplate.Triggers> 
                    </ControlTemplate> 
                </Setter.Value> 
            </Setter> 
        </Style> 
    </ResourceDictionary> 
     

    Here we are declaring a default control template for our control - this is very similar to the previous control template we defined but we've used Binding to hook up the Image Source property to the OriginalImage and MouseOverImage properties we defined on the control - we are using a special sort of Binding called TemplateBinding that allows control templates to efficiently bind to properties on their owning control. Note: For some reason I don't understand I can't do this in the Trigger - I've had to use a slightly different syntax to do the same thing.

    Now we've defined our control we can use it - for example:


    <Window x:Class="WpfApplication65.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="Window1" Height="300" Width="300"  
        xmlns:
    cc="clr-namespace:WpfApplication65">  
    <!-- REMEMBER TO CHANGE WpfApplication65 TO THE RIGHT VALUE -->
          
        <Grid> 
            <cc:ImageButton OriginalImage="/WpfApplication65;component/RedBitmap.bmp" 
                            MouseOverImage="/WpfApplication65;component/BlueBitmap.bmp"   
                            /> 
        </Grid> 
     
    </Window> 



    - mark(bou), Microsoft. (This post is provided "as-is")
    • Marked as answer by makri Wednesday, June 04, 2008 5:01 PM
    Tuesday, June 03, 2008 11:35 PM

All replies

  • This forum is specifically for the WPF Designer ("Cider") in Visual Studio. You may want to re-post your question in the WPF forum http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=119&SiteID=1 for a better chance to get your question answered.
    Marco Goertz \ Sr. Dev Lead \ WPF Designer "Cider" \ Microsoft
    Tuesday, June 03, 2008 4:32 PM
  • This forum is for the WPF Designer ("Cider") in Visual Studio. If you have questions about WPF or using the WPF runtime then the WPF forum http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=119&SiteID=1 is probably a better place to ask your question.

    Having said that, and I don't know whether this will help with your perf issue the "more typical" WPF approach for your scenario is to "re-style" the Button using ControlTemplates and Triggers - see http://msdn.microsoft.com/en-us/library/ms745683.aspx - something like 

    <Window.Resources>
        <ControlTemplate x:Key="ImageButtonTemplate" TargetType="{x:Type Button}">
            <Image x:Name="Image1" Source="RedBitmap.bmp"/>
            <ControlTemplate.Triggers>
                
    <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Image1" Property="Source" Value="BlueBitmap.bmp"/>
                </Trigger>
            </ControlTemplate.Triggers>
         </ControlTemplate>
    </Window.Resources>

    <Grid>
        <Button Template="{StaticResource ImageButtonTemplate}" />
    </Grid>



    mark



    - mark(bou), Microsoft. (This post is provided "as-is")
    Tuesday, June 03, 2008 5:26 PM
  • I guess Marco beat me to it....
    - mark(bou), Microsoft. (This post is provided "as-is")
    Tuesday, June 03, 2008 5:27 PM
  • Thanks for your suggestions guys, I will give it a try
    Mike
    Tuesday, June 03, 2008 5:48 PM
  • Mark Boulter said:

    I guess Marco beat me to it....


    - mark(bou), Microsoft. (This post is provided "as-is")



    By almost 1 hour - but you provided an alternative, so let's call it even.  ;-)
    Marco Goertz \ Sr. Dev Lead \ WPF Designer "Cider" \ Microsoft
    Tuesday, June 03, 2008 5:50 PM
  •  And here is a "less correct" but simpler solution still using Triggers:

     

    <Image > 
        <Image.Style> 
            <Style TargetType="Image">  
                <Setter Property="Source" Value="/WpfApplication65;component/RedBitmap.bmp"/>  
                <Style.Triggers> 
                    <Trigger Property="IsMouseOver" Value="False" > 
                        <Setter Property="Source" Value="/WpfApplication65;component/BlueBitmap.bmp"/>  
                    </Trigger> 
                </Style.Triggers> 
            </Style>      
        </Image.Style> 
    </Image> 
     

     


    - mark(bou), Microsoft. (This post is provided "as-is")
    Tuesday, June 03, 2008 5:52 PM
  • Hi again,

    I tried the first solution u suggested and it's working great, I have a small question though, will it be possible to modify the different values of the image source ("RedBitmap.bmp" and "BlueBitmap.bmp") from the code behind?

    I am planning on using this control more that once in my project and I wanted to create a user control with it.

    Thanks again.

    Mike
    Tuesday, June 03, 2008 7:54 PM
  • Is it that you want a property on the UserControl - say "MouseOverImage" - that can be used to set the image to use on MouseOver?
    - mark(bou), Microsoft. (This post is provided "as-is")
    Tuesday, June 03, 2008 8:52 PM
  • yes I have 2 properties in the code behind:

    1) OriginalImage
    2) MouseOverImage

    and instead of doing Source="RedBitmap.bmp" or Source=BlueBitmap.bmp" in the ControlTemplate, I want to do Source=OriginalImage and Source=MouseOverImage.

    Mike
    Tuesday, June 03, 2008 9:20 PM
  • OK this gets a little more complicated - taking the "no code approach" - the best way to do this is to use a control that derives from Button and with a custom control template.

    1. In VS add a new WPF custom control "Custom Control (WPF)" - this will add CustomControl1.cs and Themes\Generic.xaml

    2. Rename CustomControl1.cs to ImageButton.cs

    3. In WPF if you want to be able to bind, use animations etc. you have to create DependencyProperties instead of ordinary CLR properties. So let's go ahead and do that for your 2 properties. Also we want our own control template not the default one Button gets so we'll override that too using DefaultStyleKeyProperty - giving this:

        public class ImageButton : Control  
        {  
            public static readonly DependencyProperty MouseOverImageProperty;  
            public static readonly DependencyProperty OriginalImageProperty;  
     
            static ImageButton() {  
                    DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));  
                    MouseOverImageProperty = DependencyProperty.Register("MouseOverImage"typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));  
                    OriginalImageProperty = DependencyProperty.Register("OriginalImage"typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));  
            }  
     
            public ImageSource MouseOverImage {  
                get { return (ImageSource)GetValue(MouseOverImageProperty); }  
                set { SetValue(MouseOverImageProperty, value); }  
            }  
     
            public ImageSource OriginalImage {  
                get { return (ImageSource)GetValue(OriginalImageProperty); }  
                set { SetValue(OriginalImageProperty, value); }  
            }  
     
        }  
     

    4. Now you can go the generic.xaml and add a default template for your control:

    <ResourceDictionary  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApplication65">  
    <!-- REMEMBER TO CHANGE WpfApplication65 TO THE RIGHT VALUE -->
     
     
        <Style TargetType="{x:Type local:ImageButton}">  
            <Setter Property="Template">  
                <Setter.Value> 
                    <ControlTemplate TargetType="{x:Type local:ImageButton}">  
                        <Image x:Name="Image1" Source="{TemplateBinding OriginalImage}"/>  
                        <ControlTemplate.Triggers> 
                            <Trigger Property="IsMouseOver" Value="True">  
                                <Setter TargetName="Image1" Property="Source" Value="{Binding Path=MouseOverImage, RelativeSource={RelativeSource TemplatedParent}}"/>  
                            </Trigger> 
                        </ControlTemplate.Triggers> 
                    </ControlTemplate> 
                </Setter.Value> 
            </Setter> 
        </Style> 
    </ResourceDictionary> 
     

    Here we are declaring a default control template for our control - this is very similar to the previous control template we defined but we've used Binding to hook up the Image Source property to the OriginalImage and MouseOverImage properties we defined on the control - we are using a special sort of Binding called TemplateBinding that allows control templates to efficiently bind to properties on their owning control. Note: For some reason I don't understand I can't do this in the Trigger - I've had to use a slightly different syntax to do the same thing.

    Now we've defined our control we can use it - for example:


    <Window x:Class="WpfApplication65.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="Window1" Height="300" Width="300"  
        xmlns:
    cc="clr-namespace:WpfApplication65">  
    <!-- REMEMBER TO CHANGE WpfApplication65 TO THE RIGHT VALUE -->
          
        <Grid> 
            <cc:ImageButton OriginalImage="/WpfApplication65;component/RedBitmap.bmp" 
                            MouseOverImage="/WpfApplication65;component/BlueBitmap.bmp"   
                            /> 
        </Grid> 
     
    </Window> 



    - mark(bou), Microsoft. (This post is provided "as-is")
    • Marked as answer by makri Wednesday, June 04, 2008 5:01 PM
    Tuesday, June 03, 2008 11:35 PM
  • Thanks for the example, the image button works great, I just changed the custom control class to inherit from the Button object instead of Control.

    Btw are there any known issues with WPF, with images being in a certain format or a certain size? I'm asking this because whenever I use this control in my app it takes a very long time to load. So I tried replacing the control with a simple button and the problem disappeared.

    I'm using jpg images for each button about 30 Kb each (original and mouseover).

    After the application loads for the first time everything runs very smoothly but that first load is taking way too long.

    Thanks again for all your help.


    Mike
    Wednesday, June 04, 2008 5:25 PM
  • At this point I'm afraid you hit the limits of my knowledge - I'd suggest asking your performance question on the WPF forum  http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=119&SiteID=1
    mark
    - mark(bou), Microsoft. (This post is provided "as-is")
    Friday, June 06, 2008 6:07 PM
  • This is very nice when the bitmaps are embedded in the resources.  I need to make an ImageButton (like for e.g. toolbar style button) that loads the image from a file in the apps subfolder.  How does one go about doing this?  So far all the theories have either crashed or had a blank button.
    Wednesday, August 19, 2009 12:10 AM
  • Hey BigOldSofty,

    the bitmaps don't have to be embedded in the resources for this to work.

    If you take Mark's example, all you need to do is change this:

    <Window x:Class="WpfApplication65.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="Window1" Height="300" Width="300"  
        xmlns:cc="clr-namespace:WpfApplication65">  
    <!-- REMEMBER TO CHANGE WpfApplication65 TO THE RIGHT VALUE -->
          
        <Grid> 
            <cc:ImageButton OriginalImage="/WpfApplication65;component/RedBitmap.bmp" 
                            MouseOverImage="/WpfApplication65;component/BlueBitmap.bmp"   
                            /> 
        </Grid> 
     
    </Window>
    
    


    With this:

    <Window x:Class="WpfApplication65.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="Window1" Height="300" Width="300"  
        xmlns:cc="clr-namespace:WpfApplication65">  
    <!-- REMEMBER TO CHANGE WpfApplication65 TO THE RIGHT VALUE -->
          
        <Grid> 
            <cc:ImageButton OriginalImage="../MY_IMAGE_FOLDER/MY_ORIGINAL_IMAGE.jpg" 
                            MouseOverImage="../MY_IMAGE_FOLDER/MY_MOUSEOVER_IMAGE.jpg"   
                            /> 
        </Grid> 
     
    </Window>
    
    

    Make sure the path to your image is correct, otherwise you're gonna end up with an empty image button as you mentioned.
    Mike
    Thursday, August 20, 2009 1:37 PM