Binding double property to AngleProperty - c#

Is there a way to control rotation of rectangle via Binding ? I tried it this way but it does not work any idea ?
// class Unit
private double _rotation;
public double rotation
{
get
{
return _rotation;
}
set
{
_rotation = value;
OnPropertyChanged("rotation");
}
}
public Binding rotationBinding { get; set; }
// Controller class generating UI
private Rectangle GenerateUnit(Unit u)
{
Rectangle c = new Rectangle() { Width = u.size, Height = u.size };
c.Fill = new ImageBrush(new BitmapImage(new Uri(#"..\..\Images\tank\up.png", UriKind.Relative)));
c.SetBinding(Canvas.LeftProperty, u.xBinding);
c.SetBinding(Canvas.TopProperty, u.yBinding);
RotateTransform rt = new RotateTransform();
BindingOperations.SetBinding(rt, RotateTransform.AngleProperty, u.rotationBinding);
c.LayoutTransform = rt;
return c;
}
X and Y bindings work fine so I guess that is implemented correctly.
I am just looking for a way to bind the angle property so when I change the rotation property it will rotate the rectangle in UI. (I dont need animation, switching the angle instantly is fine).
Thanks

Problem seems to be in your rotationBinding. You should create binding in your Unit class:
rotationBinding = new Binding("rotation");
rotationBinding.Source = this;// or instance of o your Unit class if you create rotationBinding outside Unit class
It works for me...

I would advise against creating the binding in code when you can do it all through XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Margin="10">
<Slider x:Name="AngleProvider"
VerticalAlignment="Bottom"
Minimum="0"
Maximum="360" />
<Rectangle Fill="Blue"
Margin="100"
RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<RotateTransform Angle="{Binding Value, ElementName=AngleProvider}" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Window>
This shows a Rectangle element in the centre of the window, and a Slider control at the bottom. Drag the slider to change the rotation angle of the rectangle.
What you should bind to is the angle of rotation. Here I am using the slider to provide that angle, but it could obviously come from a property in the code-behind of the window or a ViewModel or whatever.

Related

Access GUI object method from ViewModel C#

I'm trying to create a "fake" drag animation on a chart. The goal is to allow the user to manually edit points from a graph by dragging them.
The idea is fairly simple: I put a canvas on top of the graph and upon clicking on one of the graph's point, a red circle becomes visible on the clicked mark value, as well as 2 lines coming from the previous and next graph point. I "just" have to set the opacity of each shape from 0 to 1 and specify their position (XAML code):
<Canvas Name="GraphOverlay" Grid.Row="1">
<Ellipse Canvas.Left="{Binding ElipseCanvasLeft}" Canvas.Top="{Binding ElipseCanvasTop}" Stroke="{Binding ShapesColor}" StrokeThickness="6" Width="10" Height="10" Opacity="{Binding ShapesOpacity}"/>
<Line X1="{Binding leftX1}" X2="{Binding leftX2}" Y1="{Binding leftY1}" Y2="{Binding leftY2}" Stroke="{Binding ShapesColor}" StrokeThickness="3" Opacity="{Binding ShapesOpacity}"/>
<Line X1="{Binding rightX1}" X2="{Binding rightX2}" Y1="{Binding rightY1}" Y2="{Binding rightY2}" Stroke="{Binding ShapesColor}" StrokeThickness="3" Opacity="{Binding ShapesOpacity}"/>
</Canvas>
So far I can easily get the graph values from the point I clicked on as well as the previous point and next point on the graph. However, to define the position of each shape on the cavas (1 circle and two lines), I need to convert these graph values into actual pixel values. For the circle, I just have to get the mouse position when the user clics. I am using this:
<i:Interaction.Behaviors>
<utility:MouseBehaviour MouseX="{Binding PanelX, Mode=OneWayToSource}" MouseY="{Binding PanelY, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
And the MouseBehaviour class:
public class MouseBehaviour : System.Windows.Interactivity.Behavior<FrameworkElement>
{
public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
"MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public double MouseY
{
get { return (double)GetValue(MouseYProperty); }
set { SetValue(MouseYProperty, value); }
}
public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
"MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public double MouseX
{
get { return (double)GetValue(MouseXProperty); }
set { SetValue(MouseXProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
}
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var pos = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = pos.X;
MouseY = pos.Y;
}
protected override void OnDetaching()
{
AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
}
}
And PanelX and PanelY are two properties from the ViewModel. Source
However I am struggling getting the pixel coordinate of the previous and next point. On livechart's website, there is an example that shows how to use both commands and events to access graph values. However, when using events, they have access to the ConvertToPixels method which is associated to the chart object. I don't have access to this method from the viewmodel.
Is there a way around it, or should I manually code that method ?
Edit: Here is a picture that shows what I am aiming for and what I mean by "next point", "previous point", ... Hopefully it helps!
I found a solution, I don't really know how ugly this is, but it keeps my codebehind nice and blank.
It's (very) ugly and should never pass a code review :)
Your view model now has a dependency not only on the App class but also on a particular control in a particular window that must be present on the screen for your code to work. This will for example never work in a unit test and breaks the MVVM pattern badly.
You should inject the view model with an interface and interact with the view using this interface. There is an example of how to do this available here if you are interested.
I found a solution, I don't really know how ugly this is, but it keeps my codebehind nice and blank.
To get access to that UI element, you have to first name it in the XAML:
<lvc:CartesianChart Name="Chart" >
...
</lvc:CartesianChart>
An then look for it in you app windows:
var MyChart = App.Current.Windows[0].FindName("Chart") as Chart;
I am not really at peace with the Windows[0] being hard coded, but this does the trick.

Data Binding Applied to PathGeometry

I'm trying to render a simple image by binding a PathFigureCollection defined in Code-Behind to the Figures property of a corresponding UI element. PropertyChanged is appearing as null in the debugger and the figures I'm trying to render are not appearing.
This is my first time implementing data binding so I'm guessing the issue lies with my understanding of it. Most similar issues I've found were solved by setting the DataContext variable or setting Source instead of Path in the XAML. I implemented those solutions and they don't solve my problem.
<Window x:Class="DrawingSandBox.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:DrawingSandBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="754">
<Grid Margin="0,0,0,0">
<Image HorizontalAlignment="Left" Height="400" Margin="10,10,10,10" VerticalAlignment="Bottom" Width="700" RenderTransformOrigin="0.5,0.5">
<mage.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Pen>
<Pen Thickness="11" Brush="Black"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="{Binding Path=Frame, UpdateSourceTrigger=PropertyChanged}" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
namespace DrawingSandBox
{
public partial class MainWindow : Window
{
private static readonly CurveBuilder curve = new CurveBuilder();
public MainWindow()
{
InitializeComponent();
DataContext = curve;
}
}
public class CurveBuilder : INotifyPropertyChanged
{
private PointCollection points;
private PolyBezierSegment seg;
private PathFigureCollection frame;
public event PropertyChangedEventHandler PropertyChanged;
public PathFigure Figure;
public PathFigureCollection Frame
{
get
{
return frame;
}
set
{
if (value != frame)
{
frame = value;
NotifyPropertyChanged("Frame");
}
}
}
public CurveBuilder()
{
points = new PointCollection { new Point(20, 20), new Point(40, 40) };
seg = new PolyBezierSegment(points, true);
Figure = new PathFigure(new Point(50, 50), new PathSegmentCollection { seg }, false);
Frame = new PathFigureCollection { Figure };
}
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
As it stands, this code merely displays a blank page.
You need at least 3 points for the PolyBezierSegment to show up.
According to the documentation:
A cubic Bezier curve is defined by four points: a start point, an end point and two
control points. A PolyBezierSegment specifies one or more cubic Bezier curves by
setting the Points property to a collection of points. For every three points in the
collection, the first and second points specify the two control points of the curve
and the third point specifies the end point.
The binding is correct. Add one more Point to your PointCollection or use a PolyQuadraticBezierSegment or QuadraticBezierSegment.

UWP: Reveal full, unclipped ImageBrush on tapped event

I have a series of rectangles in which the user can add images to, by dragging the images in.
The image is then scaled down in proportion and the rectangle is then filled with an ImageBrush.
I need for the user to be able to manipulate the image within the rectangle to fit their needs. Like any photo collage app does.
My question is: How can I show the full, unmasked image on top of the rectangle so that the user can manipulate it to their needs? I don't know where to start with this one.
private async void Mask_Drop(object sender, DragEventArgs e)
{
Rectangle maskSq = e.OriginalSource as Rectangle;
var maskW = maskSq.Width.ToSingle();
var maskH = maskSq.Height.ToSingle();
double maskX = Canvas.GetLeft(maskSq);
double maskY = Canvas.GetTop(maskSq);
// Image sizes for bounding to mask
float boundH = Convert.ToSingle(size.Height);
float boundW = Convert.ToSingle(size.Width);
maskSq.Fill = new ImageBrush
{
ImageSource = new BitmapImage(new Uri("ms-appdata:///local/" + SelectedImage.Name, UriKind.Absolute)),
Stretch = Stretch.UniformToFill
};
}
private void Tap_Collage(object sender, TappedRoutedEventArgs e)
{
//Gets the full image from ImageBrush
ImageBrush brush = (ImageBrush)(((Rectangle)sender).Fill);
Rectangle rect = sender as Rectangle;
//Mask sure rectangle does not drag, just the image brush
rect.CanDrag = false;
rect.StrokeThickness = 6;
//Drag Image Functionality
rect.ManipulationDelta += ImageManipulation.Resize_ImageEdit;
ImageManipulation.ImageEdit_Drag = new TranslateTransform();
brush.Transform = ImageManipulation.ImageEdit_Drag;
//Zoom Image Functionality
ImageManipulation.ImageEdit_Zoom = new ScaleTransform();
brush.RelativeTransform = ImageManipulation.ImageEdit_Zoom;
}
Class
public static class ImageManipulation
{
public static TranslateTransform ImageEdit_Drag;
public static ScaleTransform ImageEdit_Zoom;
public static RotateTransform ImageEdit_Rotate;
public static void Resize_ImageEdit(object sender, ManipulationDeltaRoutedEventArgs e)
{
ImageEdit_Drag.X += e.Delta.Translation.X;
ImageEdit_Drag.Y += e.Delta.Translation.Y;
ImageEdit_Zoom.ScaleX *= e.Delta.Scale;
ImageEdit_Zoom.ScaleY *= e.Delta.Scale;
if (ImageEdit_Zoom.ScaleX < 1.0)
{
ImageEdit_Zoom.ScaleX = 1.0;
ImageEdit_Zoom.ScaleY = 1.0;
}
ImageEdit_Rotate.Angle += e.Delta.Rotation;
}
}
XAML
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:CollageGrid">
<Rectangle Width="{Binding CollageW}" Height="{Binding CollageH}" AllowDrop="True" CanDrag="True" Fill="Transparent"
Drop="Mask_Drop"
DragOver="Mask_DragOver"
ManipulationMode="TranslateX, TranslateY" Stroke="Black" StrokeThickness="2" DragEnter="Mask_DragEnter" DragLeave="Mask_DragLeave" Tapped="Tap_Collage">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding CollageX}" Y="{Binding CollageY}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
Example of what I'm looking to acheive:
How it looks currently:
You can try to replace the Rectangle with the Polyline to draw the Rectangle so that you can access the Image which is on the bottom of the Rectangle.
<Grid>
<Image Source="Assets/image1.jpg" Width="800"
Height="400" Tapped="Image_Tapped" />
<Polyline Stroke="Black" HorizontalAlignment="Center"
VerticalAlignment="Center"
StrokeThickness="4" Tapped="Polyline_Tapped"
Points="0,0,0,200,200,200,200,0,0,0" />
</Grid>
---Update---
UniformToFill will cause the image is resized to fill the destination dimensions while it preserves its native aspect ratio. If the aspect ratio of the destination rectangle differs from the source, the source content is clipped to fit in the destination dimensions. So it is not suitable for your requirement. You should manually scale the Image to make image fit one of your Rectangle's border and keep other part out of the Rectangle.
It seems you put the image as the Rectangle's background image brush, there are no other place to display the image out of the Rectangle. So I think, we may take a considerition for a new scenario.
As my pervious reply, using Image control to display the image and Polylines to draw a Rectangle above the Image so that we can operate the Image which is below the Rectangle using the manipulation events to fit the rectangle, meanwhile we can also use the community toolkit BackdropBlurBrush on the xaml layout to Blur the outer image.

WPF - how do I bind a control's position to the current mouse position?

Is there a way to bind to the mouse position in WPF in the XAML file? Or does that have to be done in code? I have a control inside a Canvas, and I just want the control to follow the mouse while the mouse cursor is inside the Canvas.
Thanks
EDIT:
OK, I figured it out a relatively easy way using the code-behind file. I added a MouseMove event handler on the Canvas, and then added:
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
// Get the x and y coordinates of the mouse pointer.
System.Windows.Point position = e.GetPosition(this);
double pX = position.X;
double pY = position.Y;
// Sets the position of the image to the mouse coordinates.
myMouseImage.SetValue(Canvas.LeftProperty, pX);
myMouseImage.SetValue(Canvas.TopProperty, pY);
}
using http://msdn.microsoft.com/en-us/library/ms746626.aspx as a guideline.
I tried to make a kind of decorator for this purpose.
You wrap the object, mouse position above which you want to control and bind some control to decorator MousePosition property.
public class MouseTrackerDecorator : Decorator
{
static readonly DependencyProperty MousePositionProperty;
static MouseTrackerDecorator()
{
MousePositionProperty = DependencyProperty.Register("MousePosition", typeof(Point), typeof(MouseTrackerDecorator));
}
public override UIElement Child
{
get
{
return base.Child;
}
set
{
if (base.Child != null)
base.Child.MouseMove -= _controlledObject_MouseMove;
base.Child = value;
base.Child.MouseMove += _controlledObject_MouseMove;
}
}
public Point MousePosition
{
get
{
return (Point)GetValue(MouseTrackerDecorator.MousePositionProperty);
}
set
{
SetValue(MouseTrackerDecorator.MousePositionProperty, value);
}
}
void _controlledObject_MouseMove(object sender, MouseEventArgs e)
{
Point p = e.GetPosition(base.Child);
// Here you can add some validation logic
MousePosition = p;
}
}
and XAML
<local:MouseTrackerDecorator x:Name="mouseTracker">
<Canvas Width="200" Height="200" Background="Red">
<Button Width="20" Height="20" Canvas.Left="{Binding ElementName=mouseTracker, Path=MousePosition.X}" Canvas.Top="{Binding ElementName=mouseTracker, Path=MousePosition.Y}" />
</Canvas>
</local:MouseTrackerDecorator>
Tried a few examples only. The msdn documentation is , i think, incorrectly worded
"How to: Make an Object Follow the Mouse Pointer"
should be
"How to: Make an object's size increase based on mouse position"
anyway.
I was able to achieve this effect by changing the canvas properties. Also not sure why everyone was attaching the event handler to the objects next top level layout property and not the window. Maybe you and most of the examples online are going for a different effect
<Window x:Class="FollowMouse.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" MouseMove="MouseMoveHandler">
<Canvas>
<Ellipse Name="ellipse" Fill="LightBlue"Width="100" Height="100"/>
</Canvas>
code behind
private void MouseMoveHandler(object sender, MouseEventArgs e)
{
/// Get the x and y coordinates of the mouse pointer.
System.Windows.Point position = e.GetPosition(this);
double pX = position.X;
double pY = position.Y;
/// Sets eclipse to the mouse coordinates.
Canvas.SetLeft(ellipse, pX);
Canvas.SetTop(ellipse, pY);
Canvas.SetRight(ellipse, pX);
}

Scale Two Canvases Proportionally

I am trying to think of the best way to scale two panels proportionally.
If I have a grid that contains two canvases, both stacked horizontally next to each other, I want canvas (A) to scale to the size of canvas (B), proportionally, though.
So, essentially, if canvas (B) increases in size, canvas (A) decreases, and if canvas (A) increases, canvas (B) decreases.
I'm thinking of using a converter to do this, but wanted to know if anyone had any good ideas.
Below is a link that demonstrates the desired behavior. Refer to the pan/zoom control in the lower right corner of the screen. That control represents a preview of the main screen. If you press on the zoom button within the pan/zoom control, the main screen zooms in, and the rectangular "pan" area in the pan/zoom control decreases in size.
http://quince.infragistics.com/#/Search/ViewPattern$pattern=Button+Groups/PatternExamples$guid=289a497a-6632-455a-87b6-74ee70c2d3be
Thanks!
Chris
A converter is probably be the best way to go. You could also use RenderTransform.ScaleX/ScaleY instead of adjusting the Height/Width of the canvas.
Here's an example of binding to a property. Not sure if it'd be better then a converter.
<Canvas Background="Blue">
<Canvas x:Name="canvas1" ClipToBounds="True" Background="Red" Width="100" Height="100">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="{Binding ElementName=slider, Path=Value}" ScaleY="{Binding ElementName=slider, Path=Value}"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" ClipToBounds="True" Background="Green" Grid.Column="2" Height="100" Width="100" Canvas.Left="200">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="{Binding ScaleValue2}" ScaleY="{Binding ScaleValue2}"/>
</Canvas.RenderTransform>
</Canvas>
<Slider x:Name="slider" Canvas.Top="200" Width="200" Value="{Binding Path=ScaleValue, Mode=TwoWay}" Maximum="2"></Slider>
</Canvas>
Code:
public partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private double scaleValue = 1;
public double ScaleValue
{
get
{
return scaleValue;
}
set
{
scaleValue = value;
NotifyPropertyChanged("ScaleValue");
NotifyPropertyChanged("ScaleValue2");
}
}
public double ScaleValue2
{
get
{
return slider.Maximum - ScaleValue;
}
}
}

Categories

Resources