I have a Line and Textblock in my xaml as follow:
<Line X1="{Binding StartPoint.X}" Y1="{Binding StartPoint.Y}" X2="{Binding EndPoint.X}"
Y2="{Binding EndPoint.Y}" Stroke="{Binding Color}" StrokeThickness="{Binding Thickness}" />
<TextBlock Text="{Binding Title}" />
The TextBlock shows the title of line (for example "Line 1").
The above XAML draw the lines on a canvas and work correctly, but it doesn't show the TextBlock next to line and in parallel to it.
How can I change this XAML code so the text be in the centre of line and parallel to it.
I would do that by using a TextBlock and nest inside of it the Line and the TextBlock you want to use. For example:
<TextBlock Canvas.Left="147" Canvas.Top="132" Height="45">
<Line X1="10" Y1="20" X2="100" Y2="0" Stroke="Black" StrokeThickness="4"/>
<TextBlock Text="Line1" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</TextBlock>
The result of the above will be:
I attempted to solve your problem with a different approach
example below will render the line for given coordinates and will place a label next to it
I start by writing a class for LineData
class LineData : DependencyObject
{
public Point P1
{
get { return (Point)GetValue(P1Property); }
set { SetValue(P1Property, value); }
}
// Using a DependencyProperty as the backing store for P1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty P1Property =
DependencyProperty.Register("P1", typeof(Point), typeof(LineData), new PropertyMetadata(new Point(), (d, e) => (d as LineData).Update()));
public Point P2
{
get { return (Point)GetValue(P2Property); }
set { SetValue(P2Property, value); }
}
// Using a DependencyProperty as the backing store for P1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty P2Property =
DependencyProperty.Register("P2", typeof(Point), typeof(LineData), new PropertyMetadata(new Point(), (d, e) => (d as LineData).Update()));
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(LineData), new PropertyMetadata(string.Empty));
public double Length
{
get { return (double)GetValue(LengthProperty); }
set { SetValue(LengthProperty, value); }
}
// Using a DependencyProperty as the backing store for Length. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LengthProperty =
DependencyProperty.Register("Length", typeof(double), typeof(LineData), new PropertyMetadata(0.0));
public double TransformAngle
{
get { return (double)GetValue(TransformAngleProperty); }
set { SetValue(TransformAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for TransformAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TransformAngleProperty =
DependencyProperty.Register("TransformAngle", typeof(double), typeof(LineData), new PropertyMetadata(0.0));
public void Update()
{
//calculate angle
double dy = P2.Y - P1.Y;
double dx = P2.X - P1.X;
double theta = Math.Atan2(dy, dx);
theta *= 180 / Math.PI;
TransformAngle = theta - 90;
//calculate length
double aSq = Math.Pow(P1.X - P2.X, 2);
double bSq = Math.Pow(P1.Y - P2.Y, 2);
Length = Math.Sqrt(aSq + bSq);
}
}
in this class i wrote logic to convert given point into angle and length which will be used in data template to render accordingly, Update method is called when any point is changed
here comes the xaml, here l: is namespace to the line data class
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=P1.X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=P1.Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding TransformAngle}"/>
</Grid.RenderTransform>
<TextBlock Text="{Binding Label}" VerticalAlignment="Center" Margin="5,0,0,0"/>
<Line Y2="{Binding Length}" StrokeThickness="1" Stroke="Black"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<l:LineData Label="Line 1" P1="100,100" P2="135,150"/>
<l:LineData Label="Line 2" P1="150,150" P2="235,50"/>
</ItemsControl>
i used the canvas as panel of the items control
placed the item at the correct place by binding canvas.left and canvas.top to point one of the line data in style of ContentPresenter
created a data template with a grid, label and line
set the y2 of line as length of line data leaving all other points to be 0
set the label in textbox
rotated the grid based on the angle
it's done, result of the sample
since I was not sure what is actually meant by parallel to you I assumed a label which is horizontal when line is vertical (so perpendicular to line)
if you want both horizontally parallel then apply a render transform to your textblock too
<TextBlock Text="{Binding Label}" VerticalAlignment="Center" Margin="16,0,0,10">
<TextBlock.RenderTransform>
<RotateTransform Angle="90"/>
</TextBlock.RenderTransform>
</TextBlock>
you may also bind the items control to a observable collection of line data and binding will do the rest.
Alternative horizontally parallel approach
public void Update()
{
double dy = P2.Y - P1.Y;
double dx = P2.X - P1.X;
double theta = Math.Atan2(dy, dx);
theta *= 180 / Math.PI;
TransformAngle = theta; //remove the 90 degree vertial adjustment
double aSq = Math.Pow(P1.X - P2.X, 2);
double bSq = Math.Pow(P1.Y - P2.Y, 2);
Length = Math.Sqrt(aSq + bSq);
}
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding TransformAngle}"/>
</Grid.RenderTransform>
<TextBlock Text="{Binding Label}" HorizontalAlignment="Center" ></TextBlock>
<Line X2="{Binding Length}" Grid.Row="1" StrokeThickness="1" Stroke="Black"/>
</Grid>
</DataTemplate>
removed vertical adjustment in calculation
using Length property for line's X2
removed margin adjustments from textblock
result is bit cleaner approach without adjustments
Related
I have a problem when I'm trying to draw a line with the MVVM-Pattern in WPF.
I followed this example: Drawing line on a canvas in WPF MVVM doesn't work
The Problem I have, is that I want to draw lines on a video control that is already implemented, which I can't use as ItemsPanelTemplate.
So my code currently looks like this:
xaml
<ContentControl>
<views:ExtendedMediaElement x:Name="MediaPlayer" Grid.Row="0" ScrubbingEnabled="True" LoadedBehavior="Manual" UnloadedBehavior="Stop"
MediaLength="{Binding MediaLength, Mode=OneWayToSource}"
MediaPosition="{Binding MediaPosition, Mode=TwoWay}"
EndPosition="{Binding EndPosition, Mode=OneWay}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cal:ActionMessage MethodName="MouseDown">
<cal:Parameter Value="$eventArgs"/>
<cal:Parameter Value="{Binding ElementName=ItemControl}"/>
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentControl>
<ItemsControl x:Name="ItemControl" ItemsSource="{Binding Path=Lines}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="{Binding Stroke}" StrokeThickness="{Binding StrokeThickness}"></Line>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ViewModel
private ObservableCollection<Line> _lines;
public ObservableCollection<Line> Lines
{
get { return _lines; }
set
{
if (_lines != value)
{
_lines = value;
NotifyOfPropertyChange("Lines");
}
}
}
public void MouseDown(MouseButtonEventArgs e, System.Windows.Controls.ItemsControl lineArea)
{
System.Windows.Point p = e.GetPosition(lineArea);
if (!isFirstPointSet)
{
firstClickPoint = p;
isFirstPointSet = true;
return;
}
Line l = new Line();
l.X1 = firstClickPoint.X;
l.Y1 = firstClickPoint.Y;
l.X2 = p.X;
l.Y2 = p.Y;
l.Stroke = System.Windows.Media.Brushes.Black;
l.StrokeThickness = 4;
Lines.Add(l);
firstClickPoint = p;
}
Now the first line I draw is always perfectly placed on the Click-Coordinates, but the second line has an offset on the X-Coordinate. The thrid line is often not even visible any more.
Thanks in advance for your help!
Niko
Take a look at the below example, each item in an ItemsControl are at different position. If the first line is drawn on canvas 32, the second line will be drawn on canvas 33. They are not drawn on the same, big canvas as you might expected.
You need to set the ItemsPanel property just like the answer in the link you provided.
I have recently started using the Kinect SDK 2.0 and am focusing on a zoom and pan functionality, as in the Control Basics-WPF sample.
I have got the zoom and pan functionality up and running. The problem is that I wish to access the value of the amount of zoom which has been performed by the Pinch zoom gesture.
Here is my xaml:
<UserControl x:Class="ImageNav.NavigationImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:k="http://schemas.microsoft.com/kinect/2014"
mc:Ignorable="d"
d:DesignWidth="1200"
d:DesignHeight="700"
>
<Grid Grid.RowSpan="2">
<ScrollViewer Name="scrollViewer" Grid.Row="0"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
k:KinectRegion.IsHorizontalRailEnabled="true" k:KinectRegion.IsVerticalRailEnabled="true"
k:KinectRegion.ZoomMode="Enabled">
<Image Name="navigationImage" RenderTransformOrigin="0.5, 0.5" />
</ScrollViewer>
<TextBox x:Name="ZoomTextBox" Grid.Row="1" TextWrapping="Wrap" Text="Zoom: 100%" IsEnabled="False" Panel.ZIndex="10" BorderThickness="0" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="20"/>
</Grid>
</UserControl>
I would have wanted there to be something like k:KinectRegion.ZoomFactor, but that isnt available. I've also tried to see what changes in the UI elements when I perform the zoom gesture, by writing the Height and ActualHeight properties of the ScrollViewer scrollViewer and Image navigationImage to a log file, but they show no change whatsoever.
When I perform the zoom gesture, I would like to get the value of zoom i.e. the current height and width of the image with respect to the original height and width.
This has nothing to do with Kinect SDK, this is more of a ScrollViewer zooming issue. There is no k:KinectRegion.ZoomFactor because zooming doesn't change the actual size of the image, it only performs some layout transformations, therefore you can get the zooming factor from LayoutTransform property of your Image.
Something like the following code should get the zooming factor:
UserControl.Code:
public NavigationImage()
{
InitializeComponent();
DataContext = this;
_zoom = 1.0;
}
double _zoom;
public string ZoomPercentage
{
get
{
return _zoom * 100 + "%";
}
}
private void scrollViewer_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
{
_zoom += 0.1;
}
else
{
_zoom -= 0.1;
}
ScaleTransform scale = new ScaleTransform(_zoom, _zoom);
navigationImage.LayoutTransform = scale;
OnPropertyChanged("ZoomPercentage");
e.Handled = true;
}
UserControl.Xaml:
<UserControl x:Class="ImageNav.NavigationImage" ... >
<Grid Grid.RowSpan="2">
<ScrollViewer Name="scrollViewer" Grid.Row="0" PreviewMouseWheel="scrollViewer_MouseWheel"
....
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
k:KinectRegion.IsHorizontalRailEnabled="true" k:KinectRegion.IsVerticalRailEnabled="true"
k:KinectRegion.ZoomMode="Enabled"
>
<Image Name="navigationImage" RenderTransformOrigin="0.5, 0.5"/>
</ScrollViewer>
<TextBox x:Name="ZoomTextBox" Grid.Row="1" Text="{Binding ZoomPercentage, Mode=OneWay}" .... />
</Grid>
</UserControl>
So I'm working with a system of switches and bulbs, which are attached together by wires. I'm supposed to make the switches draggable, in such a way that the wires also move. Since you cannot update the points directly in a PointCollection (I think they're a struct I think?), I've figured out a way, but this seems VERY verbose, and I figure there must be a better way to do this.
Basically I retrieve the points, do the manipulation, clear the collection, and add the new points:
Point firstPoint = new Point(firstPointCollection[0].X, firstPointCollection[0].Y + offset.Y);
Point secondPoint = new Point(firstPointCollection[1].X + offset.X, firstPointCollection[1].Y + offset.Y);
Point thirdPoint = new Point(firstPointCollection[2].X + offset.X, firstPointCollection[2].Y + offset.Y);
firstPointCollection.Clear();
firstPointCollection.Add(firstPoint);
firstPointCollection.Add(secondPoint);
firstPointCollection.Add(thirdPoint);
In a system with multiple wires, which all consist of multiple points, this very quickly gets very tedious to write. This must all be done in C#, but if there is a better way to do with this with Data Binding of some sort, please let me know.
well the whole thing could be done with data binding so as you update the location of a bulb it auto updates the UI, stay with me here the code example is a bit long..
the result of this demo is just some circles with lines joining them but you could template this up however you want.
The xaml window, this declares an items control that will display the bulbs (and the lines joining them)
<Window x:Class="points.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:points"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Bulbs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top">
<Ellipse Fill="Blue" Width="10" Height="10">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Ellipse.RenderTransform>
</Ellipse>
<Line X1="{Binding X}" Y1="{Binding Y}" X2="{Binding NextBulb.X}" Y2="{Binding NextBulb.Y}" Stroke="Red">
<Line.RenderTransform>
<TranslateTransform X="5" Y="5"/>
</Line.RenderTransform>
</Line>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Window>
and this is the cs, I've created a bulb structure that holds the X and Y location of a bulb and a reference to the next bulb in the chain, I've placed all bulbs in to a line and then just moved the Y location of element 5 a bit lower to show it updating.
using System.Collections.ObjectModel;
using System.Windows;
namespace points
{
public class Bulb : DependencyObject
{
public double X
{
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public static readonly DependencyProperty XProperty =
DependencyProperty.Register("X", typeof(double), typeof(Bulb), new PropertyMetadata(0d));
public double Y
{
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
public static readonly DependencyProperty YProperty =
DependencyProperty.Register("Y", typeof(double), typeof(Bulb), new PropertyMetadata(0d));
public Bulb NextBulb
{
get { return (Bulb)GetValue(NextBulbProperty); }
set { SetValue(NextBulbProperty, value); }
}
public static readonly DependencyProperty NextBulbProperty =
DependencyProperty.Register("NextBulb", typeof(Bulb), typeof(Bulb), new PropertyMetadata(null));
}
public partial class MainWindow : Window
{
public ObservableCollection<Bulb> Bulbs
{
get { return (ObservableCollection<Bulb>)GetValue(BulbsProperty); }
set { SetValue(BulbsProperty, value); }
}
public static readonly DependencyProperty BulbsProperty =
DependencyProperty.Register("Bulbs", typeof(ObservableCollection<Bulb>), typeof(MainWindow), new PropertyMetadata(null));
public MainWindow()
{
Bulbs = new ObservableCollection<Bulb>();
InitializeComponent();
for (int i = 0; i < 10; i++)
{
double x = i * 50;
double y = 25;
Bulbs.Add(new Bulb()
{
X = x,
Y = y,
NextBulb = i > 0 ? Bulbs[i - 1] : null,
});
}
Bulbs[5].Y = 50;
}
}
}
The outcome of this is that altering the Bulbs structure in any way will update the bulb display on the UI without having to clear and recreate your collection every time, you can even run animations on the properties for some pretty cool effects.
I really like the accepted answer - an upvote well deserved - because it was well written and really works. But, I thought it would be worth noting: if you intend to use it for many quick changing values, updating a PointCollection will perform better. See How to draw a simple (line) graph?.
Regarding the code and it's verbosity, maybe this would have (7 years ago) helped:
firstPointCollection = new PointCollection(firstPointCollection.Select(p=>new Point(p.X + offset.X, p.Y + offset.Y)));
Cheers ;o)
First of all, the Offset() method
As Amnestic pointed out even though Point is a struct you can mutate it through the Offset method. However since he is using a PointCollection and not an array of Points when you use the indexer you will get a copy of the struct, not the original.
If the points were in a Point[] you could just use this:
firstPointCollection[0].Offset(x, y)
However because we are using a PointCollection you need to set the indexer as such:
firstPointCollection[0] = firstPointCollection[0].Offset(x, y)
The discussion over whether structs should be mutable or not is a whole other kettle of fish.
I have this xaml:
<Canvas cal:View.Context="DrawCanvas">
<!--<Line X1="1" Y1="1" X2="400" Y2="400" Stroke="Black" StrokeThickness="20" IsHitTestVisible="False"/>-->
</Canvas>
and in model I have:
public Canvas DrawCanvas { get; set; }
public ImageSourceViewModel()
{
this.PropertyChanged += this.ImageSourceViewModel_PropertyChanged;
this.Scale = 1;
this.TranslateX = 0;
this.TranslateY = 0;
DrawCanvas=new Canvas();
var line = new Line();
line.X1= 1;
line.Y1 = 1;
line.X2 = 100;
line.Y2 = 10;
line.Stroke=new SolidColorBrush(Colors.Green);
line.StrokeThickness = 2;
line.Visibility=Visibility.Visible;
DrawCanvas.Children.Add(line);
}
I'm using Caliburn Micro.
It doesn't draw any line on output.
There could be two reason for this problem:
1- The canvas on view is not bind to DrawCanvas in ViewModel.
2- The drawing code is not correct.
How can I check that the my view canvas is actually bind to DrawCanvas in my ViewModel? Is the syntax for binding correct? I am using Caliburn Micro.
If the binding is correct, what the problem with drawing code that it is not working?
That is the way you can do it in MVVM (I modified the solution from here: https://stackoverflow.com/a/1030191/3047078):
In the view:
<ItemsControl ItemsSource="{Binding Path=Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White" Width="500" Height="500" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="Black" StrokeThickness="3"></Line>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In the ViewModel, you need something like this:
public ObservableCollection<MyLine> Lines {get;set;}
In the Model:
public class MyLine
{
public int X1 {get;set;}
public int Y1 {get;set;}
public int X2 {get;set;}
public int Y2 {get;set;}
}
I'm working on my first WPF application and I'm testing out a custom control that is essential a circle with a play button drawn into the middle of it. I seem to have hit a bit of a hitch though. When I draw my play button I can't seem to get it to resize alongside the circle. Specifically, when I resize the circle to be wider or taller, the play button polygon remains the same size and in the same absolute position. Any pointer on setting up my XAML or code to correct this?
Existing XAML:
<Window x:Class="WPFTest.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" xmlns:my="clr-namespace:WPFTest">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="my:GradientButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:GradientButton}">
<Grid>
<Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Stroke="{TemplateBinding Foreground}" VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="{TemplateBinding GradientStart}" Offset="0"></GradientStop>
<GradientStop Color="{TemplateBinding GradientEnd}" Offset="1"></GradientStop>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Polygon Points="{TemplateBinding PlayPoints}" Fill="{TemplateBinding Foreground}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<my:GradientButton Content="Button" Height="50" x:Name="gradientButton1" Width="50" GradientStart="#FFCCCCCC" GradientEnd="#FFAAAAAA" PlayPoints="18,12 35,25 18,38" />
</StackPanel>
</Window>
Code:
public class GradientButton : Button
{
internal static DependencyProperty GradientStartProperty;
internal static DependencyProperty GradientEndProperty;
internal static DependencyProperty PlayPointsProperty;
static GradientButton()
{
GradientStartProperty = DependencyProperty.Register("GradientStart", typeof(Color), typeof(GradientButton));
GradientEndProperty = DependencyProperty.Register("GradientEnd", typeof(Color), typeof(GradientButton));
PlayPointsProperty = DependencyProperty.Register("PlayPoints", typeof(PointCollection), typeof(GradientButton));
}
public Color GradientStart
{
get { return (Color)base.GetValue(GradientStartProperty); }
set { SetValue(GradientStartProperty, value); }
}
public Color GradientEnd
{
get { return (Color)base.GetValue(GradientEndProperty); }
set { SetValue(GradientEndProperty, value); }
}
public PointCollection PlayPoints
{
get
{
//this is where I'm trying to make the resizing dynamic, but this property never seems to get hit when I put in a breakpoint?
PointCollection result = new PointCollection();
double top = this.Width / 2.778;
double left = this.Width / 4.167;
double middle = this.Height / 2.00;
double right = this.Width / 1.429;
double bottom = this.Height / 1.316;
result.Add(new Point(left, top));
result.Add(new Point(right, middle));
result.Add(new Point(left, bottom));
return result;
}
set { SetValue(PlayPointsProperty, value); }
}
}
You need to set the Stretch property of the Polygon to Uniform