Silveright ScrollViewer with Image and ScaleTransform - silverlight

I have the following xaml.
<ScrollViewer HorizontalAlignment="Stretch" Margin="107,0,0,0" Name="scrollViewer1" VerticalAlignment="Stretch" HorizontalScrollBarVisibility="Visible">
<Image Name="image1" Stretch="None" MouseWheel="image1_MouseWheel" RenderTransformOrigin="0,0">
</Image>
</ScrollViewer>
An the following code behind.
// initialise.
private TransformGroup group = new TransformGroup();
private ScaleTransform st = new ScaleTransform();
group.Children.Add(st);
image1.RenderTransform = group
// mouse event.
TransformGroup group = (TransformGroup)image1.RenderTransform;
ScaleTransform scale = (ScaleTransform)group.Children.Last();
double zoom = e.Delta > 0 ? .2 : -.2;
scale.ScaleX += zoom;
scale.ScaleY += zoom;
How do I get the scroller to take into account that the image is now a different size.
The scroll bars remain the same size, and I cannot work out how to change them.
Thanks

You need the LayoutTransformer from the Silverlight Toolkit. Instead of setting a RenderTransform on your Image, put it inside a LayoutTransformer.

have you tried calling InvalidateScrollInfo on the scrollviewer?

Related

Alternate between "Stretch.Uniform" and "Stretching.None" for ViewBox with ScrollViewer

I want to achieve a very well known behavior seen in the browser when you have an image to display that is larger then the monitor:
Originally, the image is displayed fitting inside the window area, and the mouse cursor is a magnifying glass with a "+" icon;
If you click, two things happen:
a. The image is displayed with its native pixel size;
b. Scroll bars appear;
I want this effect with a larger-than-screen UniformGrid. For that, I can use ViewBox. I have already got what I want putting the control inside a ViewBox with Stretch.Uniform property, and upon MouseLeftButtonDown event it toggles between Stretch.None and Stretch.Uniform, just like the large image in browser analogy, only without scroll bars.
Now if I add the ScrollViewer (ViewBox -> ScrollViewer -> UniformGrid), the effect doesn't work anymore, because the ScrollViewer always displays the (larger than window) MyUserControl with its native resolution, that is, clipped and with scroll bars activated, while I would like to alternate between this and a "fitting in ViewBox" version.
Here is how I get the resizing, but the ScrollViewer never displays:
<Viewbox x:Name="vbox" Stretch="None">
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</ScrollViewer>
</Viewbox>
And if change the order, then the Scroll bars always display and the zoom doesn't toggle upon mouse click (although the event fires):
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<Viewbox x:Name="vbox" Stretch="None">
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</Viewbox>
</ScrollViewer>
And here the code behind event:
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (vbox.Stretch == Stretch.None)
{
vbox.Stretch = Stretch.Uniform;
}
else
vbox.Stretch = Stretch.None;
}
So what am I doing wrong, or what should I do so that the intended behavior works?
The way I see it, I would like to alternate between having the control in a ViewBox (Stretch.Uniform) and having the control inside a ScrollViewer, but I wonder how to have the same effect with both elements being part of the layout tree (one inside another), or even if I should, move the UniformGrid in and out of containers I would manipulate programmatically in code behind.
Got it to work in sort of a hackish way, by having a Grid with both a ViewBox and a ScrollViewer, and putting the UniformGrid inside one of them in XAML. Then, in code-behind, I programmatically detach the UniformGrid from its present container, and attach it to the other (using a boolean flag to control where it is, but that is debatable):
<Grid x:Name="grid">
<ScrollViewer x:Name="scroll" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<Viewbox x:Name="viewbox" Stretch="Uniform">
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</Viewbox>
</Grid>
and
bool atlasfullscreen = false;
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UniformGrid ug = sender as UniformGrid;
if (atlasfullscreen)
{
scroll.Content = null;
viewbox.Child = ug;
atlasfullscreen = false;
}
else
{
viewbox.Child = null;
scroll.Content = ug;
atlasfullscreen = true;
}
}
I had a similar use case where I had an item that I needed to alternate between Stretch.None and Stretch.Uniform, and when Stretch.None, I needed the scrollbars to be visible.
What I finally figured out was that when I set Stretch.None, I needed to set the ScrollViewer's Width & Height to the ViewBox's parent ActualWidth / Height, and when Stretch.Uniform, I needed to clear the ScollViewer's width and height.
So using your original XAML, plus the new Grid, here's the new XAML:
<Grid x:Name="grid">
<Viewbox x:Name="vbox"
Stretch="Uniform">
<ScrollViewer x:Name="scroll"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<UniformGrid x:Name="ugrid"
Columns="2"
MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior />
<local:AtlasMasculinoPosterior />
</UniformGrid>
</ScrollViewer>
</Viewbox>
</Grid>
New code behind:
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (vbox.Stretch == Stretch.None)
{
vbox.Stretch = Stretch.Uniform;
scroll.Width = double.NaN;
scroll.Height = double.NaN;
}
else
{
vbox.Stretch = Stretch.None;
scroll.Width = grid.ActualWidth;
scroll.Height = grid.ActualHeight;
}
}
You might need to tweak the above example for how the Viewbox now being in a grid - but for my use case with similar XAML / code I got mine working without having to constantly move the child from the Viewbox to another control and back again.
So in summary: when Viewbox.Stretch = Uniform, set scrollviewer's width / height to double.NaN, and when Viewbox.Stretch = None, set scrollviewer's width / height to Viewbox.Parent.ActualWidth / Height.

WPF Transform Rectangle in Canvas to Selection in Image

I have a rectangle on a canvas that the user can resize, move and so on to make a selection.
I also have an image the size of the screen behind the canvas (basically a screenshot).
I'd like to translate the selection (the rectangle) in the canvas to a 1:1 selection in the image (I want the image directly behind the rectangle) given I have the rectangle's Canvas.Top, Canvas.Left, Width, Height.
<Grid Name="MainGrid" SnapsToDevicePixels="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image x:Name="MainImage" Stretch="None" RenderOptions.BitmapScalingMode="HighQuality"/>
<Border Background="Black" Opacity="0.4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<Canvas Name="MainCanvas" Width="{Binding Source={x:Static SystemParameters.PrimaryScreenWidth}}" Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight}}" Background="Transparent">
<ContentControl Name="SelectionRect" />
</ContentControl>
</Canvas>
</Grid>
I tried doing this: (MainImage is the image under the canvas)
Rect rect = new Rect(Canvas.GetLeft(SelectionRect), Canvas.GetTop(SelectionRect), SelectionRect.Width, SelectionRect.Height);
Rect from_rect = SelectionRect.TransformToVisual(this).TransformBounds(rect);
BitmapSource cropped_bitmap = new CroppedBitmap(MainImage.Source as BitmapSource,
new Int32Rect((int)from_rect.X, (int)from_rect.Y, (int)from_rect.Width, (int)from_rect.Height));
SelectionRectImageSource = cropped_bitmap;
But the image I get (SelectionRectImageSource) is a moved aside version of the actual pixels behind the selection rectangle.
So basically, I don't understand how these transformations work and how I should use them if at all.
Example:
Thanks a lot!
Dolev.
Looks like you need to correct for the DPI difference between the image (usually 72dpi) and the presentation source (usually 96dpi). Additionally, your first Rect should not be offset by Canvas.Left and Canvas.Top; TransformToVisual will take care of the relative offset for you.
var source = (BitmapSource)MainImage.Source;
var selectionRect = new Rect(SelectionRect.RenderSize);
var sourceRect = SelectionRect.TransformToVisual(MainImage)
.TransformBounds(selectionRect);
var xMultiplier = source.PixelWidth / MainImage.ActualWidth;
var yMultiplier = source.PixelHeight / MainImage.ActualHeight;
sourceRect.Scale(xMultiplier, yMultiplier);
var croppedBitmap = new CroppedBitmap(
source,
new Int32Rect(
(int)sourceRect.X,
(int)sourceRect.Y,
(int)sourceRect.Width,
(int)sourceRect.Height));
SelectionRectImageSource= croppedBitmap;
Depending on where this code resides, you may also need to transform the selection rectangle to MainImage instead of this (as I did).
Also, in case MainImage.Source is smaller than the actual MainImage control, you should probably set the horizontal and vertical alignments of MainImage to Left and Top, respectively, less your translated rectangle end up outside the bounds of the source image. You'll need to clamp the selection rectangle to the dimensions of MainImage too.

WPF ZoomControl and Adorners

This my first post on stack overflow, I hope I get it right. I am using the ZoomControl from WPF Extensions to display an image with pan and zoom support:
<DockPanel Grid.Row="1" x:Name="canvas">
<Controls:ZoomControl x:Name="zoomControl">
<Canvas x:Name="canvas">
<Image x:Name="imageControl" Stretch="None" />
</Canvas>
</Controls:ZoomControl>
</DockPanel>
When the user selects an image with a bowse dialog, I load that image like so:
bmp = new BitmapImage(new Uri(fileName));
this.imageControul.Source = bmp;
I would like to added rectangles\adorners to specific locations (pixel coordinates) on the image the user loaded based on some image processing.
var r = new Rectangle();
r.StrokeThickness = 5;
r.Stroke = Brushes.Black;
r.Fill = Brushes.Transparent;
r.Width = width;
r.Height = height;
Canvas.SetLeft(r, y);
Canvas.SetTop(r, x);
canvas.Children.Add(r);
However, the rectangles are not placed in the expected locations? Wrong scale and location.
Thanks,
John
I expect the problem is that your Canvas is expanding to fill the space rather than being locked to the rectangle. Have a look with a tool like Snoop and see what the bounding boxes of the two are.
You might be able to fix it with Horizontal and VercticalAlignment on the canvas, set them to anything other than Stretch.
If that doesn't work restructure it like this
<ZoomBox>
<Grid>
<Image/>
<Canvas/>
</Grid>
</ZoomBox>
So the Image and the canvas are grouped by the parent Grid which is being transformed.

Items not drawing on Resize of Viewbox, Silverlight

I am currently designing a layout app in Silverlight and have a Canvas inside of a Viewbox. I add shapes to the canvas and they display properly, when I resize the viewbox to zoom in at 2x the height and width, everything still draws properly.
The problem comes when I try to zoom at a factor of 4 or greater or at 0.5 (zoomed out).
Update: The horizontal lines are still there, they are just not drawing. Interaction between the the other shapes and the disappearing ones is still present
When I do this, any horizontal lines do not redraw, but any other shapes, vertical lines of other, still redraw fine. The objects are still children of the canvas and their visibilities are all set to visible.
What is happening?
Update
Very Simple XAML:
<ScrollViewer x:Name="scrollViewer"
Padding="0"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
IsTabStop="False"
Background="Beige">
<Viewbox x:Name="viewBox" Stretch="UniformToFill">
<Canvas x:Name="designCanvas"
Background="{Binding ElementName=mainControl, Path=Background, Mode=TwoWay}">
</Canvas>
</Viewbox>
</ScrollViewer>
Here is how I add the shapes:
Rectangle horGuide = new Rectangle()
{
Tag = "horGuide",
Fill = new SolidColorBrush(Colors.Cyan),
Height = 0.5,
Width = designCanvas.canvActualWidth*16,
};
int h = designCanvas.horOffset;
int v = designCanvas.vertOffset;
double d = e.GetPosition(sideRule).Y;
designCanvas.Children.Add(horGuide);
Canvas.SetTop(horGuide, ((d+v )/ designCanvas.zoomFactor));
Canvas.SetLeft(horGuide, 0 - h);
To Zoom in:
viewBox.Width *= 2;
viewBox.Height *= 2;
Why not use the ScaleTransform class to zoom in and out.

WPF how to show an Image.Source (BitmapSource) pixel position?

Let's suppose I've got an image which shows its source in a scaled way, how could i use a
MouseMove event to show in a label or textblock the pixel position in which the cursor is?
(I need the pixel coordinates not the coordinates of the image relative to its size)
Thanks in advance.
You can find out the actual pixel height and width from the ImageSource.
ImageSource imageSource = image.Source;
BitmapImage bitmapImage = (BitmapImage) imageSource ;
Now since you got the image displayed in Image control. You can easily map the mouse position to the Pixel scale.
pixelMousePositionX = e.GetPosition(image).X * bitmapImage.PixelWidth/image.Width;
pixelMousePositionY = e.GetPosition(image).Y * bitmapImage.PixelHeight/image.Height;
Have fun
Jobi Joy
If your Image's XAML as follow:
<Border Grid.Row="1" Grid.Column="0"
BorderThickness="3"
BorderBrush="BlueViolet">
<Image x:Name="Image_Box"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Source="8.jpg"
Stretch="Uniform"
MouseMove="ImageBox_OnMouseMove"
/>
</Border>
Maybe the Image control's width is double.Nan ,So we need to use ActualWidth property. so the Code as follow:
private void ImageBox_OnMouseMove(object sender, MouseEventArgs e)
{
ImageSource imageSource = Image_Box.Source;
BitmapSource bitmapImage = (BitmapSource)imageSource;
TextBoxCursor_X.Text =( e.GetPosition(Image_Box).X * bitmapImage.PixelWidth / Image_Box.ActualWidth).ToString();
TextBoxCursor_Y.Text = (e.GetPosition(Image_Box).Y * bitmapImage.PixelHeight / Image_Box.ActualHeight).ToString();
}

Resources