2

I'd like to extend Image class by adding second source. I want to define second source in XAML (like original source) and change these images when mouse enters/leaves this image.

I tried myself with:

class MainMenuImageButton : Image
    {
        public static readonly DependencyProperty Source2Property;
        public ImageSource Source2 
        {
            get { return Source2; }
            set
            {
                this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
            }
        }
        public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
        {
            this.Source = Source2;
        }
    }

But it doesn't work and I think I do it tottaly wrong. Can somebody help?

[UPDATE]

I wrote this:

class MainMenuImageButton : Image
{
    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
    {
        var source = (BitmapSource)Source;
        var x = (int)(hitTestParameters.HitPoint.X / ActualWidth * source.PixelWidth);
        var y = (int)(hitTestParameters.HitPoint.Y / ActualHeight * source.PixelHeight);
        var pixels = new byte[4];
        source.CopyPixels(new Int32Rect(x, y, 1, 1), pixels, 4, 0);
        if (pixels[3] < 10) return null;
        return new PointHitTestResult(this, hitTestParameters.HitPoint);
    }
    public ImageSource Source1
    {
        get { return GetValue(ImageSourceProperty) as ImageSource; }
        set { base.SetValue(ImageSourceProperty, value); }
    }
    public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("Source1", typeof(ImageSource), typeof(MainMenuImageButton));
    public ImageSource Source2
    {
        get { return GetValue(ImageSource2Property) as ImageSource; }
        set { base.SetValue(ImageSource2Property, value); }
    }
    public static readonly DependencyProperty ImageSource2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton));
    public MainMenuImageButton() : base() 
    {
        this.MouseEnter += new MouseEventHandler(MainMenuImageButton_MouseEnter);
        this.MouseLeave += new MouseEventHandler(MainMenuImageButton_MouseLeave);
    }

    void MainMenuImageButton_MouseLeave(object sender, MouseEventArgs e)
    {
        this.Source = this.Source1;
    }

    void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
    {
        this.Source = this.Source2;
    }
}

But sometimes it works and sometimes there is exception: "An unhandled exception of type 'System.ArgumentException' occurred in PresentationCore.dll

Additional information: The value is outside the expected range."


I'm not sure if I understood, but I tried this:

class MainMenuImageButton : Image
{
    public static readonly DependencyProperty Source2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton), new PropertyMetadata(true));
    public ImageSource Source2 
    {
        get { return (ImageSource)GetValue(Source2Property); }
        set
        {
            BitmapImage logo = new BitmapImage(new Uri(value.ToString(), UriKind.Relative));
            SetValue(Source2Property, logo); 
            this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
        }
    }
    public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
    {
        this.Source = Source2;
    }
}

And still nothing. Wham am I doing wrong?

Gordon
  • 312,688
  • 75
  • 539
  • 559
petros
  • 705
  • 1
  • 8
  • 26
  • 3
    possible duplicate of [How do I change an image on hover over in WPF?](http://stackoverflow.com/questions/1502914/how-do-i-change-an-image-on-hover-over-in-wpf) – xdumaine Aug 19 '13 at 13:54
  • One you have a loop on the get. Two why are you wiring up the event handler in the set? – paparazzo Aug 19 '13 at 15:06

4 Answers4

4

Extending Image Is an overkill, all you have to do is define a style which will use trigger to swap the sources

<Image>
  <Image.Style>
    <Style TargetType="{x:Type Image}">
      <Setter Property="Source" Value="Image1"/>
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Source" Value="Image2"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </Image.Style>
</Image>
makc
  • 2,569
  • 1
  • 18
  • 28
  • But where do I select Image1 and Image2? – petros Aug 19 '13 at 14:39
  • @petros you supply the path to `Image1` and `Image2` in your style. You can also bind to an `ImageSource` on your viewmodel or code-behind instead, if you don't want to hardcode an image resource path. – Thelonias Aug 19 '13 at 15:11
  • And if I want to use this style in more than one cotrol? Do I have to write different style for each one? (e.g. I have 2 Image controls and each one has 2 different sources) – petros Aug 19 '13 at 15:31
  • @petros Go to msdn.microsoft.com and search on wpf style. What have you tried? – paparazzo Aug 19 '13 at 15:42
  • @Blam, in your solution I have to select Image2 in style (I think so), but I want select it like: – petros Aug 19 '13 at 16:32
4

Refer to the Custom Dependency Properties article on MSDN. The event hookup belongs in your dependency property's PropertyChangedCallback.

I would also suggest using a trigger instead of event handling. However, this doesn't mean you will need to duplicate the XAML everywhere you want to use it. You could define a custom control with the image switching trigger in its default style (see "Defining Resources at the Theme Level" in the Control Authoring Overview). Where MouseOverImage is a Control with "Source" and "Source2" dependency properties, you could define this default style:

<Style TargetType="local:MouseOverImage">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MouseOverImage">
                <Grid>
                    <Image Name="SourceImage" Source="{TemplateBinding Source}" />
                    <Image Name="Source2Image" Source="{TemplateBinding Source2}" Visibility="Hidden" />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="SourceImage" Property="Visibility" Value="Hidden" />
                        <Setter TargetName="Source2Image" Property="Visibility" Value="Visible" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

If you use event handlers, you would need to store the original value of Source, add a MouseLeave handler that reverts it, and also consider the case where a user reassigns Source or Source2 at any time. Using the trigger solution with two separate "Source" and "Source2" bindings, all of this is handled automatically.

EDIT

But sometimes it works and sometimes there is exception: "An unhandled exception of type 'System.ArgumentException' occurred in PresentationCore.dll

Additional information: The value is outside the expected range."

My guess is that HitTestCore is firing after the source changes but before it's applied to the layout, so there is a discrepancy between ActualWidth and source.PixelWidth. I am not sure of the rationale for including these in the calculation (shouldn't they always be the same?) Try just using the following:

var x = (int)hitTestParameters.HitPoint.X;
var y = (int)hitTestParameters.HitPoint.Y; 
nmclean
  • 7,564
  • 2
  • 28
  • 37
  • But I also want to override HitTestCore method to react only with nontransparent field and ude it like a button. – petros Aug 19 '13 at 16:38
  • @petros It might be easier to extend Image then. I would go with a separate property to the define the "normal" source, and use [SetBinding](http://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.setbinding.aspx) in the `MouseEnter` / `MouseLeave` handlers to bind `Source` to either `Source1` or `Source2`. That way both properties can be bound independently without one overwriting the other when the mouse moves. – nmclean Aug 19 '13 at 17:26
  • I tried your version but then it was reacting only in bottom left corner, so I returned my version. But you're right that it's something with HitTestCore. I used try-catch and it doesn't throw exceptions now. But I know that try-catch solution is not the best. – petros Aug 19 '13 at 23:11
  • @petros The exception would be caused by x or y being out of range (0 to (PixelWidth - 1), and 0 to (PixelHeight - 1)). You could check for this yourself and adjust x or y to the closest valid value (minimum or maximum) when they are invalid. – nmclean Aug 20 '13 at 01:39
  • I tried source.PixelWidth-1 and source.PixelHeight-1 and it seems to work. Debugger haven't riched the breakpoint in catch block yet. Thanks for help – petros Aug 20 '13 at 10:55
1

You don't need to extend the Image class to do this. There is a property on the Image class called IsMouseOver that you can trigger on to switch the Source of your image. Put this in a style on your view and you'll be all set.

Thelonias
  • 2,918
  • 3
  • 29
  • 63
0

You need to add the new property as a Dependency Property. You can find out more from the DependencyProperties Overview page at MSDN, but the basic idea is this:

You first create the Dependency Property:

public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(
"IsSpinning", typeof(Boolean), typeof(YourClassName), new PropertyMetadata(true));

Then you can optionally add a wrapper using standard CLR properties (for your own use only):

public bool IsSpinning
{
    get { return (bool)GetValue(IsSpinningProperty); }
    set { SetValue(IsSpinningProperty, value); }
}

(Code example was taken from the linked article)

Sheridan
  • 68,826
  • 24
  • 143
  • 183