I cannot find the property or the object that I need..
I have implemented a draggable and zoomable image but I can drag it out of the screen, so I lose my Image out of the view.
Where should I put my image to keep it inside a box (and, possibly with the nice bouncing effect)?
EDIT:
Pratically my image has to remain in the stackpanel, with only the possibility of zoom-in.
(max zoom-out as the first rendering).
A bit of code:
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
initialAngle = compositeTransform.Rotation;
initialScale = compositeTransform.ScaleX;
}
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
compositeTransform.ScaleX = initialScale * e.DistanceRatio;
compositeTransform.ScaleY = initialScale * e.DistanceRatio;
}
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
compositeTransform.TranslateX += e.HorizontalChange;
compositeTransform.TranslateY += e.VerticalChange;
}
<StackPanel x:Name="container">
<Image x:Name="image_chart">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale" />
<TranslateTransform x:Name="transform" />
<CompositeTransform x:Name="compositeTransform"/>
</TransformGroup>
</Image.RenderTransform>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta"
DragDelta="OnDragDelta"/>
</toolkit:GestureService.GestureListener>
</Image>
</StackPanel>
EDIT 2 - Half Answer
I've finally find out how to stop the zoom-out at the original size!
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
if (1.0 <= (initialScale * e.DistanceRatio))
{
compositeTransform.ScaleX = initialScale * e.DistanceRatio;
compositeTransform.ScaleY = initialScale * e.DistanceRatio;
}
}
The If condition means: if I'm zooming in -> no problem because the e.DistanceRatio is >1. If I'm zooming out I will stop until the initialScale will be the same!
Now I still need help on how avoid the Drag outside.
Ok, I find out the solution, but I need some improvement.
The scaling work but when the image is on the right it goes all on the left (because it's scaling starting from the top_left corner..
Here's the code for the "blocking drag":
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
double realWidth = image_chart.ActualWidth*compositeTransform.ScaleX;
double realHeight = image_chart.ActualHeight * compositeTransform.ScaleY;
if(compositeTransform.TranslateX>=0)
compositeTransform.TranslateX = Math.Max(container.ActualWidth - realWidth,
Math.Min(0, compositeTransform.TranslateX + e.HorizontalChange));
else
compositeTransform.TranslateX = Math.Max(container.ActualWidth - realWidth,
Math.Min(0, compositeTransform.TranslateX + e.HorizontalChange));
if(compositeTransform.TranslateY>=0)
compositeTransform.TranslateY = Math.Max(container.ActualHeight - realHeight,
Math.Min(0, compositeTransform.TranslateY + e.VerticalChange));
else
compositeTransform.TranslateY = Math.Max(container.ActualHeight - realHeight,
Math.Min(0, compositeTransform.TranslateY + e.VerticalChange));
EDIT:
In the end I've decide to use the WebBrowser.. much more "smoother" and enjoyable!
my solution:
XAML code
<StackPanel x:Name="Scroll" Margin="0">
<Image CacheMode="BitmapCache" Name="FrontCover" Source="{Binding FullCover}" >
<Image.RenderTransform>
<CompositeTransform x:Name="transform" ScaleX="1" ScaleY="1" />
</Image.RenderTransform>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener PinchDelta="OnPinchDelta" PinchStarted="OnPinchStarted" DragDelta="OnDragDelta" />
</toolkit:GestureService.GestureListener>
</Image>
</StackPanel>
double initialScale;
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
initialScale = transform.ScaleX;
}
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
var curZoom = initialScale * e.DistanceRatio;
if (curZoom >= 1 && curZoom <= 3)
{
transform.ScaleX = curZoom;
transform.ScaleY = curZoom;
}
}
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
transform.CenterX = (transform.CenterX - e.HorizontalChange);
transform.CenterY = (transform.CenterY - e.VerticalChange);
if (transform.CenterX < 0)
transform.CenterX = 0;
else if ( transform.CenterX > Scroll.ActualWidth)
transform.CenterX = Scroll.ActualWidth;
else if (transform.CenterX > (FrontCover.Height * transform.ScaleX))
transform.CenterX = FrontCover.Height * transform.ScaleX;
if (transform.CenterY < 0)
transform.CenterY = 0;
else if (transform.CenterY > Scroll.ActualHeight)
transform.CenterY = Scroll.ActualHeight;
else if (transform.CenterY > (FrontCover.Height * transform.ScaleY))
transform.CenterY = FrontCover.Height * transform.ScaleY;
}
Think it should help others
Assuming that at beginning image is in top-left corner (and please check if this still works after scaling):
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
compositeTransform.TranslateX = Math.Min(container.ActualWidth - image_chart.ActualWidth,
Math.Max(0,
compositeTransform.TranslateX + e.HorizontalChange));
compositeTransform.TranslateY = Math.Min(container.ActualHeight - image_chart.ActualHeight,
Math.Max(0,
compositeTransform.TranslateY + e.VerticalChange));
}
Related
I am developing an UWP application, and one of my page is actually for user to take photo of them. In the page, I have timers for user to select before they take picture.
However, I wish to have a timer shown, counting down in the camera screen, so that the user know how much time is left for them to prepare, before the picture is taken.
Any idea on how I can do that? Thank you!
Just in case it is needed, here is my codes for the timers and the take picture buttons:
private async void PhotoButton_Click(object sender, RoutedEventArgs e)
{
//If preview is not running, no preview frames can be acquired
if (!_isPreviewing) return;
await Task.Delay(TimeSpan.FromSeconds(_seconds));
await TakePhotoAsync();
await GetPreviewFrameAsSoftwareBitmapAsync();
PreviewFrameBackground.Visibility = Visibility.Visible;
}
private void Timer_3sec_Click(object sender, RoutedEventArgs e)
{
Timer_5sec.Opacity = 0.2;
Timer_7sec.Opacity = 0.2;
Timer_3sec.Opacity = 1.0;
_seconds = 3;
}
private void Timer_5sec_Click(object sender, RoutedEventArgs e)
{
Timer_3sec.Opacity = 0.2;
Timer_7sec.Opacity = 0.2;
Timer_5sec.Opacity = 1.0;
_seconds = 5;
}
private void Timer_7sec_Click(object sender, RoutedEventArgs e)
{
Timer_3sec.Opacity = 0.2;
Timer_5sec.Opacity = 0.2;
Timer_7sec.Opacity = 1.0;
_seconds = 7;
}
You can use a DispatcherTimer to solve your problem.
Here a little code sample how you can do that (The sample dont show how to take the capture or to show the remaining seconds, just to calculate them!)
Class-Parameters:
private int _startTime;
private DispatcherTimer _timer = new DispatcherTimer();
Methods:
private void StartTimer()
{
_timer.Interval = TimeSpan.FromMilliseconds(500);
_timer.Tick += Timer_Tick;
_startTime = Environment.TickCount;
_timer.Start();
}
private void Timer_Tick(object sender, object e)
{
var remainingSeconds = _seconds - TimeSpan.FromMilliseconds(Environment.TickCount - _startTime).Seconds;
if(remainingSeconds <= 0)
{
_timer.Stop();
_timer.Tick -= Timer_Tick;
timerText.Text = "0 Seconds";
//Capture Image
} else
{
timerText.Text = "" + remainingSeconds + " Seconds";
}
}
You need to call the StartTimer-Method in you Click-Methods, after setting the _seconds.
I need to have Image control in Xamarin.Forms with rounded corners. But I did not find any property that could make it. How to have circular Image ?
I use the FFImageLoading libraries CachedImage control with a circle transformation for circle images:
<ffimageloading:CachedImage
DownsampleToViewSize="true"
Aspect="AspectFill"
Source = "{Binding Image}"
LoadingPlaceholder = "{Binding DefaultImage}"
ErrorPlaceholder = "{Binding DefaultImage}">
<ffimageloading:CachedImage.Transformations>
<fftransformations:CircleTransformation />
</ffimageloading:CachedImage.Transformations>
</ffimageloading:CachedImage>
You can use Image Circle Control Plugin
<controls:CircleImage Source="{Binding Image}" Aspect="AspectFill">
<controls:CircleImage.WidthRequest>
<OnPlatform x:TypeArguments="x:Double"
iOS="55"
Android="55"
WinPhone="75"/>
</controls:CircleImage.WidthRequest>
<controls:CircleImage.HeightRequest>
<OnPlatform x:TypeArguments="x:Double"
iOS="55"
Android="55"
WinPhone="75"/>
</controls:CircleImage.HeightRequest>
</controls:CircleImage>
Read more at Project github readme
You can also use CircleImage from Xamarin-Forms-Labs project.
If you need a solution using Custom Renderers (to tweak the control the way you want), here is my implementation for that.
public class ImageCircle:Image
{
public ImageCircle ()
{
}
}
[assembly: ExportRenderer(typeof(ImageCircle), typeof(ImageCircleRenderer))]
namespace myNamespace.Droid
{
public class ImageCircleRenderer:ImageRenderer
{
public ImageCircleRenderer ()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
if ((int)Android.OS.Build.VERSION.SdkInt < 20)
SetLayerType(Android.Views.LayerType.Software, null);
}
}
protected override bool DrawChild(Canvas canvas, global::Android.Views.View child, long drawingTime)
{
try
{
var radius = Math.Min(Width, Height) / 2;
var strokeWidth = 10;
radius -= strokeWidth / 2;
//Create path to clip
var path = new Path();
path.AddCircle(Width / 2, Height / 2, radius, Path.Direction.Ccw);
canvas.Save();
canvas.ClipPath(path);
var result = base.DrawChild(canvas, child, drawingTime);
canvas.Restore();
// Create path for circle border
path = new Path();
path.AddCircle(Width / 2, Height / 2, radius, Path.Direction.Ccw);
var paint = new Paint();
paint.AntiAlias = true;
paint.StrokeWidth = 5;
paint.SetStyle(Paint.Style.Stroke);
paint.Color = global::Android.Graphics.Color.White;
canvas.DrawPath(path, paint);
//Properly dispose
paint.Dispose();
path.Dispose();
return result;
}
catch (Exception ex)
{
Console.WriteLine("Unable to create circle image: " + ex);
}
return base.DrawChild(canvas, child, drawingTime);
}
}
}
[assembly: ExportRenderer(typeof(ImageCircle), typeof(ImageCircleRenderer))]
namespace LifesTopTen.iOS
{
public class ImageCircleRenderer:ImageRenderer
{
public ImageCircleRenderer ()
{
}
private void CreateCircle()
{
try
{
double min = Math.Min(Element.Width, Element.Height);
Control.Layer.CornerRadius = (float)(min / 2.0);
Control.Layer.MasksToBounds = false;
Control.Layer.BorderColor = Color.White.ToCGColor();
Control.Layer.BorderWidth = 3;
Control.ClipsToBounds = true;
}
catch(Exception ex)
{
Console.WriteLine ("Unable to create circle image: " + ex);
}
}
protected override void OnElementChanged (ElementChangedEventArgs<Image> e)
{
base.OnElementChanged (e);
if (e.OldElement != null || Element == null)
return;
CreateCircle();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == VisualElement.HeightProperty.PropertyName ||
e.PropertyName == VisualElement.WidthProperty.PropertyName)
{
CreateCircle();
}
}
}
}
I used following code to drag and drop Button in C# and it works like charm when my Form.RightToLeftLayout=False,
but
when I set RightToLeftLayout=True
it doesnt work and move the control in wrong direction!!!
public partial class Form1 : Form
{
int xPosition;
int yPosition;
bool isDraged;
public Form1()
{
InitializeComponent();
}
private void btnMoveable_MouseDown(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.SizeAll;
xPosition = e.X;
yPosition = e.Y;
isDraged = true;
}
private void btnMoveable_MouseUp(object sender, MouseEventArgs e)
{
isDraged = false;
this.Cursor = Cursors.Default;
}
private void btnMoveable_MouseMove(object sender, MouseEventArgs e)
{
if (isDraged)
{
btnMoveable.Left = btnMoveable.Left + e.X - xPosition;
btnMoveable.Top = btnMoveable.Top + e.Y - yPosition;
}
}
}
Well, you're discovering how RightToLeft is implemented. Everything is still in their normal logical position but the coordinate system is mirror-imaged along the Y-axis. So movement along the X-axis is inverted. You'll need to accommodate that. Fix:
int dx = e.X - xPosition;
if (this.RightToLeft == RightToLeft.Yes) dx = -dx;
btnMoveable.Left = btnMoveable.Left + dx;
I have a list box defined as below in my xaml. Every item consists of a canvas with an image element inside it. I have declared ManipulationEvents for the image.
<ListBox x:Name="CategoryLB" SelectionChanged="CategoryClicked" Margin="0,131,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Canvas Width='460' Height="130" Background="#FF0D6B97" Margin="10,10,10,10" >
<Image Width='480' Height="150" Source="{Binding Page}" Stretch="None" Opacity="1" CacheMode="BitmapCache"
ManipulationDelta="ImageManipulationDelta"
ManipulationCompleted="ImageManipulationCompleted"
ManipulationStarted="ImageManipulationStarted"/>
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem is ManipulationDelta is not fired at all. Only ManipulationStarted and ManipulationCompleted gets called and hence my animation does not get the translation values. Looks straightforward to me.
These are the ManipulationEvents
private void ImageManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
FrameworkElement transformElement = ((FrameworkElement)sender) as FrameworkElement;
transformElement.SetHorizontalOffset(0);
}
private void ImageManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
FrameworkElement transformElement = ((FrameworkElement)sender) as FrameworkElement;
if (Math.Abs(e.TotalManipulation.Translation.X) > transformElement.ActualWidth / 3)
{
if (e.TotalManipulation.Translation.X < 0.0)
{
ToDoItemDeletedAction(transformElement);
}
else
{
ToDoItemCompletedAction(transformElement);
}
}
else
{
ToDoItemBounceBack(transformElement);
}
}
private void ImageManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
FrameworkElement transformElement = ((FrameworkElement)sender) as FrameworkElement;
// handle the drag to offset the element
double offset = transformElement.GetHorizontalOffset().Value + e.DeltaManipulation.Translation.X;
transformElement.SetHorizontalOffset(offset);
}
Anything that Im missing here?
your code is all right but the problem is that the delta event will not fire on the emulator and i have tested your code both on emulator and device and its working fine on device so dont worry and test your code on device ....
i have done manipulation on image which goes something like this ..
I hope this might help you ..
<Image x:Name="imgViewer" CacheMode="BitmapCache" Source="/MetroImages/mapmetro.png" Stretch="Uniform">
<Image.RenderTransform>
<ScaleTransform x:Name="scaleTrans" ScaleX="2" ScaleY="2" CenterX="150" CenterY="200" />
</Image.RenderTransform>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
PinchStarted="GestureListener_PinchStart"
PinchDelta="GestureListener_PinchDelta"
PinchCompleted="GestureListener_PinchComplete"
DragStarted="GestureListener_DragStart"
DragDelta="GestureListener_DragDelta"
DragCompleted="GestureListener_DragCompleted"/>
</toolkit:GestureService.GestureListener>
</Image>
and then in eventhandlers
private readonly DispatcherTimer m_animationTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
private double _cx, _cy;
private void GestureListener_PinchStart(object sender, PinchStartedGestureEventArgs e)
{
Point p1 = e.GetPosition(imgViewer, 0);
Point p2 = e.GetPosition(imgViewer, 1);
scaleTrans.CenterX = (p1.X + ((p2.X - p1.X) / 2));
scaleTrans.CenterY = (p1.Y + ((p2.Y - p1.Y) / 2));
_cx = scaleTrans.ScaleX;
_cy = scaleTrans.ScaleY;
}
private void GestureListener_PinchDelta(object sender, PinchGestureEventArgs e)
{
// Compute new scaling factors
double cx = _cx * e.DistanceRatio;
double cy = _cy * e.DistanceRatio;
// If they're between 1.0 and 4.0, inclusive, apply them
if (cx >= 1.0 && cx <= 4.0 && cy >= 1.0 && cy <= 4.0)
{
if ((cy - 1) < 0.1 && (cx - 1) < 0.1)
cx = cy = 1;
scaleTrans.ScaleX = cx;
scaleTrans.ScaleY = cy;
}
}
private void GestureListener_PinchComplete(object sender, PinchGestureEventArgs e)
{
}
private void GestureListener_DragStart(object sender, DragStartedGestureEventArgs e)
{
}
private void GestureListener_DragDelta(object sender, DragDeltaGestureEventArgs e)
{
scaleTrans.CenterX = (scaleTrans.CenterX - e.HorizontalChange);
scaleTrans.CenterY = (scaleTrans.CenterY - e.VerticalChange);
if (scaleTrans.CenterX < 0)
{
scaleTrans.CenterX = 0;
}
else if (scaleTrans.CenterX > imgViewer.ActualWidth)
scaleTrans.CenterX = imgViewer.ActualWidth;
if (scaleTrans.CenterY < 0)
{
scaleTrans.CenterY = 0;
}
else if (scaleTrans.CenterY > imgViewer.ActualHeight)
scaleTrans.CenterY = imgViewer.ActualHeight;
}
private void GestureListener_DragCompleted(object sender, DragCompletedGestureEventArgs e)
{
}
I got Exception of System.Windows.Markup.XamlParseException occurred in System.Windows.ni.dll,
System.ArgumentException occurred in mscorlib.ni.dll and wasn't handled before a managed/native boundary
my MainPage.xaml:
<Slider Name="timelineSlider" Margin="0,185,0,376" ValueChanged="SeekToMediaPosition"/>
<TextBlock Height="51" HorizontalAlignment="Left" Margin="27,334,0,0" x:Name="TitleTextBlock" Text="Title:" VerticalAlignment="Top" FontSize="28" Foreground="{StaticResource PhoneAccentBrush}" Width="400" />
<TextBlock Height="39" HorizontalAlignment="Left" Margin="27,383,0,0" x:Name="ArtistTextBlock" Text="Artist:" VerticalAlignment="Top" FontSize="24" FontStyle="Italic" Foreground="White" Width="400" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="37,559,0,0" x:Name="StartTextBlock" Text="00:00:00" VerticalAlignment="Top" Width="100" />
<TextBlock Height="30" HorizontalAlignment="Left" TextAlignment="Right" Margin="327,559,0,0" x:Name="EndTextBlock" Text="00:00:00" VerticalAlignment="Top" Width="88" />
<Slider HorizontalAlignment="Left" Margin="43,200,0,0" VerticalAlignment="Top" Width="395" Name="SongSlider" ManipulationCompleted="SoundSlider_ManipulationCompleted"/>
My MainPage.xaml.cs file is:
private void NotProvided()
{
if (BackgroundAudioPlayer.Instance.Track != null)
{
// show soung info
TitleTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Title
ArtistTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Artist;
// handle slider and texts
SongSlider.Minimum = 0;
SongSlider.Maximum = BackgroundAudioPlayer.Instance.Track.Duration.TotalMilliseconds;
string text = BackgroundAudioPlayer.Instance.Track.Duration.ToString();
EndTextBlock.Text = text.Substring(0, 8);
}
if (null != BackgroundAudioPlayer.Instance.Track)
{
txtCurrentTrack.Text = BackgroundAudioPlayer.Instance.Track.Title +
" by " +
BackgroundAudioPlayer.Instance.Track.Artist;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState)
{
playButton.Content = "pause";
txtCurrentTrack.Text = BackgroundAudioPlayer.Instance.Track.Title +
" by " +
BackgroundAudioPlayer.Instance.Track.Artist;
}
else
{
playButton.Content = "play";
txtCurrentTrack.Text = "";
}
}
private DispatcherTimer dispatcherTimer = new DispatcherTimer();
private void startTimer()
{
// start timer to check position of the song
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 500);
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Start();
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
// song is playing
if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState)
{
// handle slider
SongSlider.Minimum = 0;
SongSlider.Value = BackgroundAudioPlayer.Instance.Position.TotalMilliseconds;
SongSlider.Maximum = BackgroundAudioPlayer.Instance.Track.Duration.TotalMilliseconds;
// display text
string text = BackgroundAudioPlayer.Instance.Position.ToString();
StartTextBlock.Text = text.Substring(0, 8);
text = BackgroundAudioPlayer.Instance.Track.Duration.ToString();
EndTextBlock.Text = text.Substring(0, 8);
}
}
private void SoundSlider_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
// get slider value
int sliderValue = (int)SongSlider.Value;
// create timespan object with milliseconds (from slider value)
TimeSpan timeSpan = new TimeSpan(0, 0, 0, 0, sliderValue);
// set a new position of the song
BackgroundAudioPlayer.Instance.Position = timeSpan;
}
private void PrevButton_Click(object sender, RoutedEventArgs e)
{
// skip to previous song
BackgroundAudioPlayer.Instance.SkipPrevious();
// handle text and slider
playButton.Content = "Pause";
SongSlider.Value = 0;
}
private void NextButton_Click(object sender, RoutedEventArgs e)
{
// skip to next song
BackgroundAudioPlayer.Instance.SkipNext();
// handle text and slider
playButton.Content = "Pause";
SongSlider.Value = 0;
}