I am working on a WPF project, trying to create an analog clock. I have an image of a clock (without the hands) and have set it as a background. I also have images of two hands of clock, which I want to rotate keeping one end fixed (like it happens in a clock). How can I rotate the image keeping its one end fixed in C#.NET (WPF)? What I have tried is the following code:
namespace AnalogWatch
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DispatcherTimer dispatcherTimer;
private int degrees = 0;
public MainWindow()
{
InitializeComponent();
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
degrees += 5;
if (degrees > 360)
{
degrees = 0;
}
RotateTransform transform = new RotateTransform(degrees,StickImg.Width/2,StickImg.Height/2);
//StickImg.RenderTransformOrigin = new System.Windows.Point(0, 0);
StickImg.RenderTransform = transform;
}
private void Window_ContentRendered_1(object sender, EventArgs e)
{
dispatcherTimer.Start();
}
}
}
It is rotating the image but not keeping its one end fixed. What is the problem here ?
XAML is:
<Window x:Class="AnalogWatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" ContentRendered="Window_ContentRendered_1">
<Grid>
<Image x:Name="StickImg" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Margin="207,70,0,0" Source="stick.png"/>
</Grid>
</Window>
Updated.
Your code should work if clock hand exactly in the center of rectangular image.
Like this one
You can do either like you did
RotateTransform transform =new RotateTransform(degrees, StickImg.Width/2,StickImg.Height/2);
or
RotateTransform transform = new RotateTransform(degrees);
StickImg.RenderTransformOrigin = new Point(0.5, 0.5);
Use the RenderTransformOrigin of the element to set the center of rotation.
Note that the coordinates are scaled to 0..1
So to rotate around the center:
RenderTransformOrigin="0.5, 0.5"
Just make sure that the pivot of the hand is in the center of the element.
here's the code i'm using and it works like charm..as qwr suggested clock hand should be exactly in the center of rectangular image
c# code
DispatcherTimer clock = new DispatcherTimer();
public AnalogClock()
{
InitializeComponent();
clock.Interval =TimeSpan.FromMilliseconds(100);
clock.Tick += clock_Tick;
clock.Start();
}
void clock_Tick(object sender, EventArgs e)
{
double milsec = DateTime.Now.Millisecond;
double sec = DateTime.Now.Second;
double min = DateTime.Now.Minute;
double hr = DateTime.Now.Hour;
seconds.LayoutTransform = new RotateTransform(((sec / 60) * 360)+((milsec/1000)*6));
minutes.LayoutTransform = new RotateTransform((min * 360 / 60)+((sec/60)*6));
hours.LayoutTransform = new RotateTransform((hr * 360 / 12)+(min/2));
}
and the XAML code for images
<Grid Margin="-100">
<Image x:Name="clockface" RenderOptions.BitmapScalingMode="HighQuality" Source="images/panel.PNG" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="hours" RenderOptions.BitmapScalingMode="HighQuality" Source="images/hours.PNG" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" HorizontalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="minutes" RenderOptions.BitmapScalingMode="HighQuality" Source="images/minutes.PNG" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" HorizontalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="seconds" RenderOptions.BitmapScalingMode="HighQuality" Source="images/seconds.PNG" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Height="194" Margin="100" Width="194"/>
</Grid>
note the i've used the code in a separate usercontrol..
just make sure that the margin between the clock hand image and the grid i'ts contained in should be enough to make room for the image to rotate else it will displace while rotating..
hope this helps..!
Related
I'm using a rectangle drawn on an adorner to mark a region of interest on an image. The issue is that if I resize the window, the rectangle doesn't change size.
I'm new to WPF, so I've done a bunch of research, googling what I can with multiple different search terms. I actually just learned adorners that way, and I've gotten this far on that, but I've hit a wall on how to finish this last piece. I know that my problem is based in the size of the rectangle, but I don't know what to capture/look for to adjust it, since wpf resizes the actual image object on window resize, so there's no scale factor to look at.
Here's the XAML for the application I'm testing things in.
<Window x:Class="TestingAdorners.MainWindow"
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"
xmlns:local="clr-namespace:TestingAdorners"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="Btn" Source="nyan.png" Stretch="Uniform"/>
</AdornerDecorator>
</Grid>
</Window>
The adorner class:
class RoiAdorner : Adorner
{
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Brushes.Green, 5);
drawingContext.DrawRectangle(null, pen, rectangle);
}
}
And the Xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AdornerLayer.GetAdornerLayer(Btn).Add(new RoiAdorner(Btn));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
The desired result is that the rectangle scales with the image object so that it always covers the same region of the image. The problem is I don't know how to capture a scale factor to scale it up and down as the window resizes.
Update: After thinking through Frenchy's suggestion I realized the answer is simply: "Normalize your coordinates"
you just adapt your render method like this:
class RoiAdorner : Adorner
{
public double factorX = 0d;
public double factorY = 0d;
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
if (factorY == 0)
factorY = rectangle.Height / AdornedElement.DesiredSize.Height;
if (factorX == 0)
factorX = rectangle.Width / AdornedElement.DesiredSize.Width;
var r = new Rect(new Size(AdornedElement.DesiredSize.Width * factorX, AdornedElement.DesiredSize.Height * factorY));
//Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
drawingContext.DrawRectangle(null, new Pen(Brushes.Red, 5), r);
}
this.AdornedElement.DesiredSize gives you the size of image.
The approach I would use is to render the picture and rectangle to the same thing. Then that one thing is stretched, scaled or whatever.
One way to do this would be to use a DrawingImage. Drawing methods are extremely efficient if rather low level.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="img" Stretch="Uniform">
<Image.Source>
<DrawingImage PresentationOptions:Freeze="True">
<DrawingImage.Drawing>
<DrawingGroup>
<ImageDrawing Rect="0,0,595,446" ImageSource="DSC00025.jpg"/>
<GeometryDrawing Brush="Green">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</AdornerDecorator>
</Grid>
Another is with a visualbrush. Controls inherit from visual - this is somewhat higher level coding.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Rectangle Name="rec">
<Rectangle.Fill>
<VisualBrush Stretch="Uniform">
<VisualBrush.Visual>
<Grid Height="446" Width="595">
<Image Source="DSC00025.jpg" Stretch="Fill"/>
<Rectangle Height="30" Width="100" Fill="Green"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</AdornerDecorator>
</Grid>
Note that both of these are quick and dirty illustrations to give you the idea. The image I picked at random off my hard drive is sized 446 * 595. You could calculate sizes or bind or stretch as suits your requirement best.
I have created an extended splash screen for Windows 10 UWP app using following XAML and C# Code.
XAML Code
<Grid Background="#036E55">
<Canvas>
<Image x:Name="extendedSplashImage" Source="Assets/620.scale-200.png"/>
</Canvas>
<ProgressRing Name="splashProgressRing"
IsActive="True"
Width="20"
Height="20"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Foreground="White"
Margin="20">
</ProgressRing>
</Grid>
C# Code
internal Rect splashImageRect;
private SplashScreen splash;
internal bool dismissed = true;
internal Frame rootFrame;
private double ScaleFactor;
ApplicationDataContainer userSettings = ApplicationData.Current.LocalSettings;
JsonDataHandler dataHandler;
//bool isZipUpdateInProgress = false;
public ExtendedSplashScreen(SplashScreen splashscreen, bool loadState)
{
this.InitializeComponent();
Window.Current.SizeChanged += new WindowSizeChangedEventHandler(ExtendedSplash_OnResize);
ScaleFactor = (double)DisplayInformation.GetForCurrentView().ResolutionScale/100;
//System.Diagnostics.Debug.WriteLine("ScaleFactor - " + ScaleFactor + "/n");
splash = splashscreen;
if (splash != null)
{
splash.Dismissed += new TypedEventHandler<SplashScreen, Object>(DismissedEventHandler);
splashImageRect = splash.ImageLocation;
PositionImage();
//PositionRing();
}
rootFrame = new Frame();
}
void PositionImage()
{
extendedSplashImage.SetValue(Canvas.LeftProperty, splashImageRect.X);
extendedSplashImage.SetValue(Canvas.TopProperty, splashImageRect.Y);
extendedSplashImage.Height = splashImageRect.Height / ScaleFactor;
extendedSplashImage.Width = splashImageRect.Width / ScaleFactor;
}
void PositionRing()
{
splashProgressRing.SetValue(Canvas.LeftProperty, splashImageRect.X + (splashImageRect.Width * 0.5) - (splashProgressRing.Width * 0.5));
splashProgressRing.SetValue(Canvas.TopProperty, (splashImageRect.Y + splashImageRect.Height + splashImageRect.Height * 0.1));
}
void ExtendedSplash_OnResize(Object sender, WindowSizeChangedEventArgs e)
{
// Safely update the extended splash screen image coordinates. This function will be executed when a user resizes the window.
if (splash != null)
{
// Update the coordinates of the splash screen image.
splashImageRect = splash.ImageLocation;
PositionImage();
// If applicable, include a method for positioning a progress control.
PositionRing();
}
}
Now, it works fine if I keep rotation mode on, but when I switch it off, and if I rotate the screen to landscape mode then the logo differs. I am using 620x300 image.
You can try with this code it might help you to get result as you want, First remove all positioning image code and only try with this
<Grid Background="#036E55">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="Assets/620.scale-200.png" Stretch="None"/>
<ProgressRing IsActive="True" Height="30" Width="30" Margin="0,10,0,0" Foreground="White"/>
</StackPanel>
</Grid>
Image and ProgressRing will manage by StackPanel in center of screen and also Image will not differ. Hope it will help you.
I have a textblock control on canvas which can be dragged horizontally to the right correctly as shown in the first and second image.
Then after I a 90 degree rotation angle is applied to its CompositeTransform, dragging the textblock to the right actually move it vertically towards the top as shown by the third and fourth image. What am I missing?
public CompositeTransform CurrentTransform = new CompositeTransform();
.....
TextBlock.RenderTransform = CurrentTransform;
....
private double angle;
public double Angle
{
get
{
return angle;
}
set
{
if (angle != value)
{
angle = value;
CurrentTransform.CenterX = 0;
CurrentTransform.CenterY = 0;
CurrentTransform.Rotation = angle;
}
}
}
The moving of the textbox is handled inside
private void CanvasText_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
{
CurrentTransform.TranslateX += e.DeltaManipulation.Translation.X;
CurrentTransform.TranslateY += e.DeltaManipulation.Translation.Y;
}
For those who are in the same boat, I managed to fix this by attaching external gesture listener from Windows Phone toolkit instead of using the built-in CanvasText_ManipulationDelta event. The textbox dragging works correctly even after rotation.
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="ImageOriginal"
Source="{Binding WbPreview, Mode=TwoWay}"
Stretch="Uniform"/>
<Grid x:Name="GridDraw"
Tap="GridDraw_Tap"
Background="Transparent"/>
<Canvas x:Name="CanvasText">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Tap="GestureListener_Tap"
DragDelta="GestureListener_DragDelta"/>
</toolkit:GestureService.GestureListener>
</Canvas>
</Grid>
I want to create a wheel control for my school project using Visual studios 2012.
What I have right now is the image and it is able to rotate when I click a button and stop at random positions.
However, I am not sure how to detect the position when the image stops spinning.
This is the spin button event where the image spins when I click the button.
private void SpinBtn_Click(object sender, RoutedEventArgs e)
{
var ease = new PowerEase { EasingMode = EasingMode.EaseOut };
Random rng = new Random(Guid.NewGuid().GetHashCode());
//DoubleAnimation(FromValue. ToValue, Duration)
DoubleAnimation myanimation = new DoubleAnimation
(0, rng.Next(360,720), new Duration(TimeSpan.FromSeconds(3)));
//Adding Power ease to the animation
myanimation.EasingFunction = ease;
RotateTransform rotate = new RotateTransform();
img.RenderTransform = rotate;
img.RenderTransformOrigin = new Point(0.5, 0.5);
rotate.BeginAnimation(RotateTransform.AngleProperty, myanimation);
}
How do I detect the position of the image (where the pointer points) once it stops spinning? So that when the pointer is pointing to that object, I can drag and drop the words into the specific textbox.
Refer to image.
you dont need to know where the image(pointer) points,just calculate the spinning degree.
double degree = rng.Next(360, 720);
DoubleAnimation myanimation = new DoubleAnimation
(0, degree, new Duration(TimeSpan.FromSeconds(3)));
double result_degree = degree % 360;
now, you can get the target object with result_degree .
for example:
you have 12 objects in a circle like a clock,so the 1st object's degree is 0 to 29, 2nd is 30 to 59....
or you can set objects' degree by yourself, 1st is 0 to 9, 2nd is 10 to 39...
about drag and drop:
I give you a simple example:
<Grid>
<TextBox x:Name="tbResult" HorizontalAlignment="Left" AllowDrop="True" Height="23" Margin="416,245,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<TextBlock x:Name="tb1" HorizontalAlignment="Left" MouseLeftButtonDown="MyMouseLeftButtonDown" Margin="280,210,0,0" TextWrapping="Wrap" Text="ob1" VerticalAlignment="Top"/>
<TextBlock x:Name="tb2" IsEnabled="False" HorizontalAlignment="Left" MouseLeftButtonDown="MyMouseLeftButtonDown" Margin="280,245,0,0" TextWrapping="Wrap" Text="ob2" VerticalAlignment="Top"/>
<TextBlock x:Name="tb3" IsEnabled="False" HorizontalAlignment="Left" MouseLeftButtonDown="MyMouseLeftButtonDown" Margin="280,281,0,0" TextWrapping="Wrap" Text="ob3" VerticalAlignment="Top"/>
</Grid>
private void MyMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TextBlock tb = sender as TextBlock ;
if(tb != null && tb.IsEnabled == true)
{
switch(tb.Name)
{
case "tb1" :
DragDrop.DoDragDrop(tb1, tb1.Text, DragDropEffects.Copy);
break;
case "tb2":
DragDrop.DoDragDrop(tb2, tb2.Text, DragDropEffects.Copy);
break;
case "tb3":
DragDrop.DoDragDrop(tb3, tb3.Text, DragDropEffects.Copy);
break;
}
}
}
when you get result_degree and know which object is selected, set it's IsEnable = true and others IsEnable = false , set TextBox's AllowDrop = true.in this example, just allow user drag ob1.
I try to rotate a Border and have the MainWindow change his size based on the new space taken by the Border rotation.
I've set SizeToContent="WidthAndHeight" but window size does't take change when I rotated the border.
Do I need to programmatically set the Width and Height for the MainWindow or this can be achieved changing the xaml code in some other way?
My xaml code:
<Window x:Class="MyClass.MainWindow"
WindowStyle="None" AllowsTransparency='True'
Topmost='False' Background="Transparent" ShowInTaskbar='False'
SizeToContent="WidthAndHeight" WindowStartupLocation="Manual">
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
</Border>
</Windows>
My c# code on Window_KeyDown:
# RotateTransform rt = new RotateTransform() is declared at class level.
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.RenderTransform = rt;
}
Use LayoutTransform instead of RenderTransform
From MSDN: Transforms Overview
LayoutTransform – A transform that is applied before the layout pass. After the transform is applied, the layout system processes the
transformed size and position of the element.
RenderTransform – A transform that modifies the appearance of the element but is applied after the layout pass is complete. By using the
RenderTransform property instead of the LayoutTransform property, you
can obtain performance benefits.
Example
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
<Border.LayoutTransform>
<RotateTransform Angle="90"/>
</Border.LayoutTransform>
</Border>
So in your case
RotateTransform rt = new RotateTransform(0.0, 0.5, 0.5);
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.LayoutTransform = rt;
}
}}