Drawing line on a canvas in WPF MVVM doesn't work - c#

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;}
}

Related

Set Canvas Background, Width and Height with MVVM

I have a struggle, I don't know how to set the background and size of a canvas with respect to MVVM, currently, I handle this only in the View part, and work ok, over this image (canvas) I want to draw some rectangle, but I want to be able to do everything according to MVVM pattern.
var bmp = new BitmapImage(new Uri(filename, UriKind.Relative));
ImageBrush brush = new ImageBrush();
brush.ImageSource = bmp;
canvas.Width = bmp.PixelWidth;
canvas.Height = bmp.PixelHeight;
canvas.Background = brush;
canvas.SnapsToDevicePixels = true;
The view model could expose a property with e.g. the image file path
public string ImagePath { get; set; }
to which you would bind like this
<Canvas Width="{Binding Background.ImageSource.PixelWidth,
RelativeSource={RelativeSource Self}}"
Height="{Binding Background.ImageSource.PixelHeight,
RelativeSource={RelativeSource Self}}"
SnapsToDevicePixels="True">
<Canvas.Background>
<ImageBrush ImageSource="{Binding ImagePath}"/>
</Canvas.Background>
</Canvas>
The conversion from string to ImageSource would automatically be performed by an ImageSourceConverter instance in WPF.
The Bindings would be simpler when the view model exposes a property of type BitmapSource:
public BitmapSource Image { get; set; }
XAML:
<Canvas Width="{Binding Image.PixelWidth}"
Height="{Binding Image.PixelHeight}"
SnapsToDevicePixels="True">
<Canvas.Background>
<ImageBrush ImageSource="{Binding Image}"/>
</Canvas.Background>
</Canvas>

Sending a grid in a C# class method

Here is the problem. I have to do a wpf project where I draw geometrical shapes. I used xaml to draw my shapes but I want their size to be generated randomly. For example a square : I have a class square that has an init() method to generate the square value, and a class draw(). But I don't know what to put in the draw method, I thought using GridName.Height = value but I can't access the GridName. Is there a way to have it in the method, here is the code :
`public void Dessin() { }
public void Init() {
Random Rand = new Random();
float randomFloat = (float)Rand.NextDouble();
randomFloat = randomFloat * 10;
UneValeur = randomFloat;
}`
and here is the xaml :
<Grid Name="Page2" Visibility="Hidden" Background="#FF373030">
<Grid x:Name="Forme" Height="300" Width="300" Margin="255,60,238.333,60.667">
<Rectangle Fill="Red" Visibility="Visible"/>
</Grid>
<Label Name ="LblTest" Content="L'aire de l'ellipse est égale à" HorizontalAlignment="Left" Margin="115,295,0,0" VerticalAlignment="Top" Height="36" Width="275" Background="#FFE9DBDB" FontSize="15"/>
</Grid>

Drawing Lines in ItemsControl in WPF offsets new drawn Lines

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.

Updating points in PointCollection

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.

How to print a text next to a line in xaml

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

Categories