Zoom of image with scrollviewer in WPF - c#

I have an Image viewer implemented using ScrollViewer
.XAML
<ScrollViewer Name="viewImg" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<Image Name="bigImage"
Source="/JoyOs.Media;component/img/Plugins/Photos/PHOTO.png"
PreviewMouseLeftButtonUp="ImageMouseLeftButtonUp"
PreviewMouseLeftButtonDown="ImageMouseLeftButtonDown"
PreviewMouseMove="ImageMouseMove" Stretch="Uniform"></Image>
</ScrollViewer>
ScrollViewer class .cs file
public partial class ImageList
{
private void ImageMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var img = (Image)sender;
img.ReleaseMouseCapture();
}
private void ImageMouseMove(object sender, MouseEventArgs e)
{
var img = (Image)sender;
if (!img.IsMouseCaptured) return;
Vector v = _startImgMove - e.GetPosition(viewImg);
viewImg.ScrollToHorizontalOffset(_originImgMove.X - v.X);
viewImg.ScrollToVerticalOffset(_originImgMove.Y - v.Y);
}
private void ImageMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var img = (Image)sender;
img.CaptureMouse();
_startImgMove = e.GetPosition(viewImg);
_originImgMove = new Point(viewImg.HorizontalOffset, viewImg.VerticalOffset);
}
}
I want zoom features implemented in it.Hence I tried to use ZoomBorder.cs class from this.
How to use ZoomBorder.cswith ScrollViewer ?
I tried Without ScrollViewer with only ZoomBorder.cs like this.
<local:ZoomBorder x:Name="border" ClipToBounds="True" Background="Gray">
<Image Name="bigImage"
Source="/JoyOs.Media;component/img/Plugins/Photos/PHOTO.png"
PreviewMouseLeftButtonUp="ImageMouseLeftButtonUp"
PreviewMouseLeftButtonDown="ImageMouseLeftButtonDown"
PreviewMouseMove="ImageMouseMove" Stretch="Uniform"></Image>
</local:ZoomBorder>

I can't answer the question for ZoomBorder's, but I have implemented zoom functionality myself in a different way. I have a ViewBox, which I bind the ScaleTransform of to a value in my ViewModel. Changing the value in my ViewModel then causes the contents of the ViewBox to zoom in and out depending on the value.
<Viewbox x:Name="vwbxZoom" Stretch="None">
<Viewbox.LayoutTransform>
<ScaleTransform ScaleX="{Binding ZoomLevel.Value}"
ScaleY="{Binding ZoomLevel.Value}" />
</Viewbox.LayoutTransform>
</ViewBox>

Here is a quick example. You may use any control as content of the Viewbox
<DockPanel>
<Slider x:Name="ZoomSlider" DockPanel.Dock="Bottom" Minimum="0.05" Maximum="2">
</Slider>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<Viewbox Stretch="Uniform" VerticalAlignment="Center" HorizontalAlignment="Center">
<Border Width="2000" Height="2000">
<Border.Background>
<LinearGradientBrush StartPoint="0, 0.5" EndPoint="1, 0.5">
<GradientStop Color="Red" Offset="0.33"/>
<GradientStop Color="Green" Offset="0.66"/>
<GradientStop Color="Blue" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
</Border>
<Viewbox.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=ZoomSlider, Path=Value}" ScaleY="{Binding ElementName=ZoomSlider, Path=Value}"/>
</Viewbox.LayoutTransform>
</Viewbox>
</ScrollViewer>
</DockPanel>

Related

Copy wpf imageSource to another wpf imageSource

I want to copy imageSource to another imageSource. When A window contains image is closed, then I want to make image in A will be copied to B window image and then open B window.
I tried to test with below code. but it wasn`t work.
ADDED :
I upload more code about relevant image code. but im sorry that i cant upload full source code but If you ask, I will edit the related source and upload it.
A a = new A();
B b;
a.Closing += delegate {
b = new B();
b.img.Source = a.img.Source;
b.Show();
};
a.Show();
<!-- a.img xml -->
<Grid x:Name="ImageGrid" Margin="0,36,10,10">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="ScaleTransform"
ScaleX="{Binding ZoomRate}" ScaleY="{Binding ZoomRate}"/>
<RotateTransform/>
</TransformGroup>
</Grid.LayoutTransform>
<Image Name="img" HorizontalAlignment="Left" VerticalAlignment="Top"
Source="{Binding BitmapSource, Mode=TwoWay}"/>
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top"
Name="canvas2"
Background="Transparent"
Width="{Binding Path=ActualWidth, ElementName=imgBefore2}"
Height="{Binding Path=ActualHeight, ElementName=imgBefore2}"
MouseLeftButtonDown="Canvas2_MouseLeftButtonDown"
MouseMove="Canvas2_MouseMove"
MouseLeftButtonUp="Canvas2_MouseLeftButtonUp"
MouseRightButtonDown="Canvas2_MouseRightButtonDown">
</Canvas>
</Grid>
<!-- b.img xml -->
<Grid x:Name="ImageGrid"
Background="Transparent"
Width="{Binding MaxWidth, ElementName=ScrollViewer}"
Height="{Binding MaxHeight, ElementName=ScrollViewer}"
MouseMove="Canvas_MouseMove"
MouseLeftButtonDown="Canvas_MouseLeftButtonDown"
MouseLeftButtonUp="Canvas_MouseLeftButtonUp"
MouseWheel="Canvas_MouseWheel">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ZoomRate}" ScaleY="{Binding ZoomRate}"/>
<RotateTransform/>
</TransformGroup>
</Grid.LayoutTransform>
<Image x:Name="img" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top"
x:Name="canvas"
Background="Transparent"
Width="{Binding ActualWidth, ElementName=imgBefore}"
Height="{Binding ActualHeight, ElementName=imgBefore}"/>
</Grid>
public Mat src;
public BitmapSource BitmapSource {
get
{
return bitmapSource;
}
set
{
bitmapSource = value;
OnPropertyChanged("bitmapSource");
} // ViewModel Code
}
private void LoadImage(System.Drawing.Bitmap bm)
{
src = OpenCvSharp.Extensions.BitmapConverter.ToMat(bm);
OriginWidth = src.Width;
OriginHeight = src.Height;
BitmapSource = OpenCvSharp.Extensions.BitmapSourceConverter.ToBitmapSource(src);
} // Load Image for a.img

Elements loaded via XamlReader() and from MainWindow.Xaml behave differently?

I have the following Viewbox in MainWindow.Xaml:
<Viewbox x:Name="R1C1Viewbox" Grid.Row="1" Grid.Column="2"
Grid.ColumnSpan="1" Grid.RowSpan="1">
<Border x:Name="R1C1Border" BorderBrush="White" CornerRadius="3"
BorderThickness="2" Background="White"
Height="40" Width="40" Margin="0,3,3,0">
<Grid x:Name="R1C1Grid">
<TextBlock x:Name="R1C1TextBlock1" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Arial" FontWeight="Bold" Text="+" FontSize="22"></TextBlock>
<Polygon x:Name="R1C1LED"
Points="0,0 15,0 0,15"
Stroke="#FFED1C24"
StrokeThickness="1">
<Polygon.Fill>
<RadialGradientBrush>
<GradientStop Color="White" Offset="2.5"/>
<GradientStop Color="Black"/>
</RadialGradientBrush>
</Polygon.Fill>
</Polygon>
<Button x:Name="R1C1Button" Background="Transparent"
BorderBrush="Transparent">
</Button>
</Grid>
</Border>
</Viewbox>
In my code behind, I define 2 RadialGradientBrushes for the Polygon.Fill property:
public static RadialGradientBrush ledOn = new RadialGradientBrush();
public static RadialGradientBrush ledOff = new RadialGradientBrush();
ledOn.GradientStops.Add(new GradientStop(Colors.Red, .8));
ledOn.GradientStops.Add(new GradientStop(Colors.White, 0));
ledOff.GradientStops.Add(new GradientStop(Colors.White, 2.5));
ledOff.GradientStops.Add(new GradientStop(Colors.Black, 0));
If I load the viewbox only from MainWindow.xaml, I can use:
R1C1LED.Fill = ledOn;
or
R1C1LED.Fill = ledOff;
and the fill changes as expected. If I load the exact same xaml from a file using XamlReader(), the Viewbox displays exactly as expected but using the code behind to change the fill as above doesn't change the fill and no errors are generated.
Example code:
private void OnTest(object sender, RoutedEventArgs e)
{
var xaml =
#"<Window
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
mc:Ignorable='d'
Title='WPF App' Height='450' Width='800'>
<StackPanel Orientation='Vertical'>
<Button x:Name='R1C1LED' Content='Load Data' />
</StackPanel>
</Window>";
var win = (Window)XamlReader.Parse(xaml);
var button = (Button)win.FindName("R1C1LED");
button.Background = Brushes.Red;
button.Click += (obj, args) => { MessageBox.Show("Hi!"); };
win.Show();
}

Resizing only a clipped area of an User Control in WPF

I have a predefined User Control from a third party library. I would like to cut off a region from this User Control and use it in the Main window. Clipping is easily done with the Clip property of a UIElement but then the resizing does not work for this single segment. Is it possible to cut from a UIElement or User Control and use only this region for measuring, resizing and rendering of the component?
Please see a sample below :
XAML
<Window x:Class="WpfStackOverflow.Window12"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfStackOverflow"
Title="Window12" Height="300" Width="600">
<Canvas>
<Button Content="Press" Height="35" Width="75" Canvas.Left="29" Canvas.Top="43" Click="Button_Click_1">
<Button.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FF62CFB6" Offset="0"/>
<GradientStop Color="#FFEBEBEB" Offset="0.5"/>
<GradientStop Color="#FF91B3E4" Offset="0.391"/>
<GradientStop Color="#FFF11278" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
<Button.Clip>
<GeometryGroup>
<EllipseGeometry RadiusX="50" RadiusY="25"/>
<RectangleGeometry Rect="55 20 75 35"/>
</GeometryGroup>
</Button.Clip>
</Button>
<Path x:Name="Pth" Width="75" Height="35" Fill="AliceBlue" Canvas.Top="80" Canvas.Left="300" Stretch="None" Stroke="#FF7C4444" Data=""/>
<Thumb Width="10" Height="10" Canvas.Left="217" Canvas.Top="34" DragDelta="Thumb_DragDelta_1"/>
<TextBlock Canvas.Left="217" TextWrapping="Wrap" Text="Drag this Thumb to right" Canvas.Top="10"/>
<TextBlock Canvas.Left="10" TextWrapping="Wrap" Text="Click this button" Canvas.Top="10"/>
</Canvas>
</Window>
CODE
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace WpfStackOverflow
{
/// <summary>
/// Interaction logic for Window12.xaml
/// </summary>
public partial class Window12 : Window
{
public Window12()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var c = VisualTreeHelper.GetClip(sender as Button);
Pth.Data = c;
}
double scaleX=1, scaleY=1;
private void Thumb_DragDelta_1(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
if (Pth.LayoutTransform == null)
Pth.LayoutTransform = new ScaleTransform(1, 1);
scaleX += e.HorizontalChange;
scaleY += e.HorizontalChange;
Pth.LayoutTransform = new ScaleTransform(scaleX, scaleY);
System.Diagnostics.Debug.WriteLine((1 + e.HorizontalChange).ToString());
Thickness margin = (sender as Thumb).Margin;
(sender as Thumb).Margin = new Thickness(margin.Left + e.HorizontalChange, margin.Top, margin.Right, margin.Bottom);
}
}
}

Get value of canvas mustn't return null

In MainPage.xaml I have 2 canvas controls (bd1 and ball) that represent 2 layers.
<Canvas x:Name="ContentCanvas" Width="354" Height="669">
<Canvas.Background>
<RadialGradientBrush>
<GradientStop Color="#FF9D0A0A" Offset="0" />
<GradientStop Color="Black" Offset="1" />
</RadialGradientBrush>
</Canvas.Background>
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="bd1" Width="370" Height="480"
Clip="F1 M 0,0L 640,0L 640,480L 0,480L 0,0" MouseMove="Canvas_MouseMove" HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<TransformGroup>
<RotateTransform Angle="0" CenterX="0.5" CenterY="0.5" />
<TranslateTransform X="0" Y="0" />
<ScaleTransform ScaleX="1" ScaleY="1" />
<SkewTransform AngleX="0" AngleY="0"/>
</TransformGroup>
</Canvas.RenderTransform>
<Path....../>
........
</Canvas>
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="ball"
Width="54" Height="52.5" Clip="F1 M 0,0L 54,0L 54,52.5L 0,52.5L 0,0"
MouseMove="Canvas_MouseMove" HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<TransformGroup>
<RotateTransform Angle="0" CenterX="0.5" CenterY="0.5" />
<TranslateTransform X="0" Y="0" />
<ScaleTransform ScaleX="1" ScaleY="1" />
<SkewTransform AngleX="0" AngleY="0"/>
</TransformGroup>
</Canvas.RenderTransform>
<Canvas Width="640" Height="480.425" Canvas.Left="0" Canvas.Top="0">
<Ellipse x:Name="Ellipse" Width="54" Height="52.5" Canvas.Left="4.17233e-007" Canvas.Top="2.03252e-005" Stretch="Fill" StrokeLineJoin="Round" Stroke="#FFFF0000">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0.0633299,0.762378" EndPoint="0.93667,0.237621">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#FF000000" Offset="0"/>
<GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Canvas>
</Canvas>
For that I created a class called Layer that has a Canvas property :
public class Layer
{
public Layer() { }
public Layer(Canvas canvas, Tipologie tip)
{
ThisCanvas = canvas; Tip = tip;
}
public string Name { get; set; }
private Canvas canv;
public Canvas ThisCanvas
{
get { return canv; }
set
{
canv = value;
if (value != null)
Name = value.GetValue(Canvas.NameProperty) as string;
}
}
}
In the MainPage class I have a global collection of Layer type. Initially I put in the collection 2 layers.
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
Layers[0] = new Layer(); Layers[0].ThisCanvas = GetTemplateChild("bd1") as Canvas;
Layers[1] = new Layer(); Layers[1].ThisCanvas = ball;
LayerSelector.ItemsSource = Layers;
}
Layer[] Layers = new Layer[2];
The problem is that bd1 and ball canvas controls are null . I also tried to put the code in the constructor of the page but it was the same problem.
I can't explain that.
Am I doing something wrong? Thanks in advance!
If you pass the Canvas in a constructor, then it will never be null unless you reassign it somewhere else.
public class Layer {
public Layer(Canvas canvas) {
this.canvas = canvas;
}
private Canvas canvas;
public Canvas ThisCanvas {
get { return canvas; }
set { canvas = value; }
}
...
}
...
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e) {
Layers[0] = new Layer(GetTemplateChild("bd1") as Canvas);
Layers[1] = new Layer(ball);
LayerSelector.ItemsSource = Layers;
}
Layer[] Layers = new Layer[2];

How do I set the size of elements in a user control according to property settings on the user control?

To make this simple: I have a user control that consists of 2 rectangles. At design time the user of the control sets the width of the user control and a default value for one of the rectangles which is a property of the user control. I want to treat the default value as a percentage and set the width of one of the rectangles to that percentage of the other rectangles width. The kind of difficulty I'm having is that I cannot get the width of the outer rectangle to set the other rectangles width as a percentage of (because everything seems to be 0 or NaN). Here's some code:
User Control:
<Grid x:Name="LayoutRoot" Background="White">
<Rectangle x:Name="OuterRectangle" Fill="Red"/>
<Rectangle x:Name="InnerRectangle" Fill="Blue"/>
</Grid>
User Control code behind:
public partial class ucRectangles : UserControl
{
public Double Percent { get; set; }
public ucRectangles()
{
InitializeComponent();
InnerRectangle.Width = Percent / 100 * OuterRectangle.ActualWidth;
}
}
Main Page:
<Grid x:Name="LayoutRoot" VerticalAlignment="Center">
<local:ucRectangles Width="400" Height="40" Percent="50"/>
</Grid>
Why don't you get the Grid to do all this for you that is what its good at:-
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<Rectangle x:Name="OuterRectangle" Fill="Red" Grid.ColumnSpan="2"/>
<Rectangle x:Name="InnerRectangle" Fill="Blue" />
</Grid>
Now just fiddle with the star values of the column definitions, here is my implementation of the Percent dependency property:-
#region public double Percent
public double Percent
{
get { return (double)GetValue(PercentProperty); }
set { SetValue(PercentProperty, value); }
}
public static readonly DependencyProperty PercentProperty =
DependencyProperty.Register(
"Percent",
typeof(double),
typeof(ShowCase1),
new PropertyMetadata(50.0, OnPercentPropertyChanged));
private static void OnPercentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ShowCase1 source = d as ShowCase1;
double percent = (double)e.NewValue;
source.LayoutRoot.ColumnDefinitions[0].Width = new GridLength(percent, GridUnitType.Star);
source.LayoutRoot.ColumnDefinitions[1].Width = new GridLength(100 - percent, GridUnitType.Star);
}
#endregion public double Percent
Note its the last two lines where the magic happens.
ActualWidth would only be relevant after the Measure pass has taken place. You're trying to do it in the C-tor, which would be WAY before the Measure is happening.
Try and use the Event LayoutUpdated, which is bound to happen after the layout process finished.
Handle (user control's) LayoutRoot's loaded event and move code inside it.
User Control XAML
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
User Control Code Behind
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
InnerRectangle.Width = Percent / 100 * OuterRectangle.ActualWidth;
}
I know this seems answered already, but here's something similar I did last year for creating simple bar charts. Just bind your percentage to the "Content" property and you'll get a auto-resizing bar without all the extra code...
<ContentPresenter Content="0.3" Height="30"> <!-- Bind the "Content" to your percentage -->
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Grid Background="YellowGreen">
<Rectangle Fill="Purple" Margin="0,5,0,5" HorizontalAlignment="Stretch" RenderTransformOrigin="0.5,0.5">
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#FFFFFFFF" Offset="0"/>
<GradientStop Color="#FFFFFFFF" Offset="{Binding}"/>
<GradientStop Color="#00000000" Offset="{Binding}"/>
<GradientStop Color="#00000000" Offset="1"/>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>

Categories