0

I am making a Silverlight 4 website with C#. In one of the pages, I want to have two panels beside each other. On the left is a map control and on the right is an image. That is easy enough, but I also want to be able to click on the image and leave PushPin like objects (like the map) so I put the image in a canvas and just draw circles. The problem is that image can be fairly large and I need to be able to scroll the image. I tried several different ways of achieving this, but so far no luck.

The answers to the following post seemed to be like the way to go, but there must be updates to Silverlight that broke it: WPF: How to make canvas auto-resize?

A similar solution suggested making the Canvas from scratch, but I ran into the same problem.

Most of my attempts end in displaying as much of the image as possible on the screen, but no scroll bars (still greyed out) or the page just goes white when the image is loaded.

The following is how I am currently selecting the image to load:

        OpenFileDialog dialog = new OpenFileDialog();
        dialog.Filter = "Image Files (*.png, *.jpg)|*.jpg;*.png";
        if(dialog.ShowDialog() == true) {
            BitmapImage bitmap = new BitmapImage();
            FileStream stream = dialog.File.OpenRead();
            bitmap.SetSource(stream);
            TheImage.Source = bitmap;
        }
Kara
  • 6,115
  • 16
  • 50
  • 57
Bryan Watts
  • 1,415
  • 12
  • 23

2 Answers2

1

There is probably be a nicer solution but this should do the trick.

I have created a small fixed size ScrollViewer that contains a Canvas and an image. I then used a behaviour to modify the size of the canvas to match the size of the image. The behaviour also handles the ImageOpened event to set the correct size of the image once the image is opened.

Here is the xaml:

<ScrollViewer Width="200" Height="200" HorizontalScrollBarVisibility="Auto">
    <Canvas x:Name="TheCanvas">
        <Image x:Name="TheImage">
            <i:Interaction.Behaviors>
                <Views:ResizeCanvasBehaviour Canvas="{Binding ElementName=TheCanvas}"/>
            </i:Interaction.Behaviors>
        </Image>
    </Canvas>
</ScrollViewer> 

Be sure to declare i as xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" and b matches the namespace where you place the behaviour.

Here is the code for the behaviour:

public class ResizeCanvasBehaviour : Behavior<Image>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SizeChanged += AssociatedObject_SizeChanged;
        AssociatedObject.ImageOpened += AssociatedObject_ImageOpened;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.SizeChanged -= AssociatedObject_SizeChanged;
        AssociatedObject.ImageOpened -= AssociatedObject_ImageOpened;
    }

    private void AssociatedObject_ImageOpened(object sender, RoutedEventArgs e)
    {
        BitmapSource bitmapSource = AssociatedObject.Source as BitmapSource;
        if (bitmapSource == null)
        {
            return;
        }

        AssociatedObject.Width = bitmapSource.PixelWidth;
        AssociatedObject.Height = bitmapSource.PixelHeight;

        Resize();
    }

    private void AssociatedObject_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        Resize();
    }

    public Canvas Canvas
    {
        get { return GetValue(CanvasProperty) as Canvas; }
        set { SetValue(CanvasProperty, value); }
    }

    public static readonly DependencyProperty CanvasProperty = DependencyProperty.Register(
        "Canvas",
        typeof(Canvas),
        typeof(ResizeCanvasBehaviour),
        new PropertyMetadata(null, CanvasPropertyChanged));

    private static void CanvasPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((ResizeCanvasBehaviour)d).OnCanvasPropertyChanged();
    }

    private void OnCanvasPropertyChanged()
    {
        if (Canvas != null)
        {
            Resize();
        }
    }

    private void Resize()
    {
        if ((AssociatedObject != null) && (Canvas != null))
        {
            Canvas.Width = AssociatedObject.ActualWidth;
            Canvas.Height = AssociatedObject.ActualHeight;
        }
    }
}

To load the image do something like this. I did this in code behind for speed but ideally you should put this in a view model and then data bind the image Source property in xaml:

BitmapImage bi = new BitmapImage();
bi.UriSource = new Uri("http://farm7.static.flickr.com/6149/5942401995_a5a3fd3919_z.jpg");
TheImage.Source = bi;
Paul Haley
  • 405
  • 3
  • 12
  • I had actually thought about utilizing the Behavior class like that, but I didn't think it was available for making web pages, only desktop apps. [MSDN](http://msdn.microsoft.com/en-us/library/system.windows.forms.design.behavior.behavior.aspx) has it listed under System.Windows.Forms.Design, which is for WPF. How would I go about using this since I am not making a WPF app? – Bryan Watts Nov 14 '11 at 15:45
  • You will need the [Expression Blend SDK](http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=3062) if you don't already have it, then reference System.Windows.Interactivity. I tested this in a Silverlight app so it will work ok. You could always add the SizeChanged event handler in code behind if you prefer but if you are using MVVM then go for the behaviour - it's self contained and reusable. – Paul Haley Nov 15 '11 at 08:18
  • One thing I left out of the problem above is that the user loads the image, so I won't know the size of the image ahead of time. Your solution works if the image is smaller than the 800x600, but clips the image if it is bigger. Obviously I can change the size, but only changing the size in the xaml actually affects outcome. When I try to change the size of the Image after the user loads an image, nothing changes. – Bryan Watts Nov 15 '11 at 18:48
  • I guess your solution does work for the problem that I described initially, so should I mark this as answered and post another question? Or is it ok to continue on this post? As you can probably tell, I'm kinda new here. BTW, thanks for your help and I hope you can figure out my problem. – Bryan Watts Nov 15 '11 at 18:52
  • I've just edited my answer to show how you can extend this to work with dynamically loaded images as you require. Please mark as answered if this has solved your problem. – Paul Haley Nov 16 '11 at 12:59
  • It Works! However, I also want to support loading an image from the user's computer and I can;t seem to get that to work with your solution. I edited my problem to include how I am loading an image. When I do it this way, there is no resizing. – Bryan Watts Nov 16 '11 at 15:24
0

Turns out the very minimum that I needed to do was set the Width and Height of the canvas to the PixelWidth and PixelHeight of the BitmapImage instance.

This is what Paul was doing with his solution (in a little more complicated way), but for some reason the resize event handlers would not get called when a image was loaded locally.

I had tried several different suggested solutions, but I never got the results I was wanting. This was the only solution that seemed to work.

Bryan Watts
  • 1,415
  • 12
  • 23