Converting one value to multiple properties - wpf

I'm using the following data template:
<DataTemplate>
<Grid Width="40" Height="40">
<Ellipse Width="30" Height="30" x:Name="ellipse" />
<TextBlock Text="{Binding Robot.Id}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Line X1="20" X2="40" X2="20" X2="30" x:Name="line" />
</Grid>
</DataTemplate>
I want to apply the following code to the line when a property of the DataContext changes:
void UpdateHeading(double angle)
{
var center = grid.Width/2;
var radius = ellipse.Width/2;
line.X1 = center + (radius+5)*Math.Sin(angle);
line.Y1 = center + (radius+5)*Math.Cos(angle);
line.X2 = center + (radius-5)*Math.Sin(angle);
line.Y2 = center + (radius-5)*Math.Cos(angle);
}
Note that the code needs access to the size of two other elements
What is the best way to add the code? using a value converter doesn't seem right here, since I need to convert one property to four

There is also an IMultiValueConverter
EDIT:
You should have a ViewModel with a angle property and can then bind as the following (i have only demonstrated for x1:
<DataTemplate>
<Grid Width="40" Height="40" x:Name="grid">
<Ellipse Width="30" Height="30" x:Name="ellipse" />
<TextBlock Text="{Binding Robot.Id}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Line X2="40" X2="20" X2="30" x:Name="line">
<Line.X1>
<MultiBinding Converter="{StaticResource yourConverter}" ConverterParameter="{yourns:Enum">
<Binding ElementName=grid Path=Width />
<Binding ElementName=ellipse Path=Width />
<Binding Path=Angle />
</MultiBinding>
</Line.X1>
</Line>
</Grid>
</DataTemplate>

Implement INotifyPropertyChanged in you class that is providing the DataContext and expose properties for all the values that need to be updated. Then just bind to those properties. The properties can calculate whatever values they need and the UI will get updated.
For Example:
public class Heading : INotifyPropertyChanged
{
private string name = "";
public string Name { get { return name; } set { name = value; SendPropertyChanged("Name"); } }
public int Radius { get { return GridWidth/2; } }
public double X1 { get { return Center + (Radius + 5) * Math.Sin(Angle); } }
public double X2 { get { return Center + (Radius + 5) * Math.Cos(Angle); } }
public double Y1 { get { return Center + (Radius - 5) * Math.Sin(Angle); } }
public double Y2 { get { return Center + (Radius - 5) * Math.Cos(Angle); } }
public int Center { get { return GridWidth/2; } }
private int gridWidth = 50;
public int GridWidth { get { return gridWidth; } set { gridWidth = value; } }
private double angle;
public double Angle { get { return angle; } set { angle = value; SendPropertyChanged(""); } } //Empty string to notify of all properties
public event PropertyChangedEventHandler PropertyChanged;
void SendPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then just set the binding in you template as so:
<DataTemplate>
<Grid >
<Ellipse Width="{Binding GridWidth}" Height="40" x:Name="ellipse" Fill="Green" />
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Line X1="{Binding X1}" X2="{Binding X2}" Y1="{Binding Y1}" Y2="{Binding Y2}" x:Name="line" Stroke="Black" />
</Grid>
</DataTemplate>
Hope this helps.

I decided to create my own shape.
XAML usage:
<Grid Width="40" Height="40">
<Ellipse x:Name="ellipse" Width="30" Height="30" />
<TextBlock Text="{Binding Robot.Id}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Controls:HeadingLine BoundingSize="40" ShapeSize="30" Length="10" Angle="{Binding Heading}" Stroke="Black" StrokeThickness="1" />
</Grid>
And code:
public sealed class HeadingLine : Shape
{
// Properties definitions
....
// Code based on http://www.codeproject.com/KB/WPF/wpfarrow.aspx
protected override Geometry DefiningGeometry
{
get
{
var geometry = new StreamGeometry();
using (var context = geometry.Open())
{
InternalDrawArrowGeometry(context);
}
geometry.Freeze();
return geometry;
}
}
private void InternalDrawArrowGeometry(StreamGeometryContext context)
{
var center = BoundingSize / 2;
var radius = ShapeSize / 2;
var offset = Length / 2;
var angle = Math.PI - Angle;
var x1 = center + (radius + offset) * Math.Sin(angle);
var y1 = center + (radius + offset) * Math.Cos(angle);
var x2 = center + (radius - offset) * Math.Sin(angle);
var y2 = center + (radius - offset) * Math.Cos(angle);
context.BeginFigure(new Point(x1, y1), false, false);
context.LineTo(new Point(x2, y2), true, true);
}
}

Related

How to bind two elements to single source in WPF

My scenario is that there are two controls. One in which you set up minutes and second in which you specify seconds.
Both of them should be bound to single property in view model. This property is of type string. This string is in format [hh:mm:ss]. So changing value in "minutes" control should change 'mm' portion of the string and changing the value in "seconds" control should change the 'ss' portion of the string.
Thanks in advance
Here is a 3-property ViewModel working solution if you are using TimeSpan and its range is between 0 and 59h 59s. I have not fully tested and conditions/validation will change based on requirements. I used TimeSpan.TotalSeconds because that's the resolution we needed; meaning, when setting the TimeSpan to a new value, we would just set the total number of seconds through the public property. An alternative could be to have 2 TimeSpan properties in your ViewModel, then when setting the public property, you could call _item.TotalSeconds = VMMinutes.TotalSeconds + VMSeconds.TotalSeconds.TotalSeconds. Basically you have many design options here.
MainWindow.xaml:
<Grid>
<StackPanel>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Minutes"/>
<TextBox Text="{Binding Minutes}" />
<Label Content="Seconds"/>
<TextBox Text="{Binding Seconds}" />
</StackPanel>
</Border>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Total Seconds"/>
<TextBox Text="{Binding TotalSeconds}" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ItemViewModel(new Item(new TimeSpan(0, 3, 59)));
}
}
ItemViewModel.cs:
public class ItemViewModel : INotifyPropertyChanged
{
private readonly Item _item;
public event PropertyChangedEventHandler PropertyChanged;
public ItemViewModel(Item item)
{
_item = item;
}
public string TotalSeconds
{
get
{
return _item.TotalSeconds.ToString();
}
set
{
double newTotSecs;
if(!string.IsNullOrEmpty(value))
{
if(double.TryParse(value, out newTotSecs))
{
_item.TotalSeconds = newTotSecs;
NotifyPropertyChanged();
NotifyPropertyChanged("Minutes");
NotifyPropertyChanged("Seconds");
}
}
}
}
public string Seconds
{
get
{
return (_item.TotalSeconds % 60).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totMinSec;
if(int.TryParse(Minutes, out totMinSec))
{
_item.TotalSeconds = (totMinSec * 60) + newVal;
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
public string Minutes
{
get
{
return ((int)(_item.TotalSeconds / 60)).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totSec;
if(int.TryParse(Seconds, out totSec))
{
_item.TotalSeconds = totSec + (newVal * 60);
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Item.cs:
public class Item
{
private TimeSpan _time;
public double TotalSeconds
{
get
{
return _time.TotalSeconds;
}
set
{
if(value >= 0)
{
_time = new TimeSpan(0, 0, (int)value);
}
}
}
public Item(TimeSpan time)
{
_time = time;
}
}
Note: Your other option is to use a Converter, which I haven't provided a solution for. I think it could end up being cleaner in the long run since all you really need to pass to back and forth is the converter is total number of seconds.
I would use NETScape's approach above, but encapsulate it in a user control. The user control XAML would be something like:
<UserControl>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Minutes" Grid.Row="0" Grid.Column="0"/>
<TextBox Text="{Binding InternalMinutes}" Grid.Row="0" Grid.Column="1"/>
<TextBlock Text="Seconds" Grid.Row="1" Grid.Column="0"/>
<TextBox Text="{Binding InternalSeconds}" Grid.Row="1" Grid.Column="1"/>
</Grid>
</UserControl>
Then in the code-behind, you would have a Dependency Property for the actual DateTime object, and properties to bind against (you could use a view model for this, or just go off of TextChanged. When its all View logic, its ok!).
An example property would be:
public int InternalSeconds
{
get { return ExternalTime.Seconds; }
set
{
ExternalTime.Seconds = value;
NotifyPropertyChanged();
}
}
Again, there are multiple approaches here, you could use a converter in order to use an intermediate object. ExternalTime is the DP here, make sure to handle its Changed event if you expect the value to change outside of this control.

drawing new lines in an already populated canvas

So I got on my grid a canvas which as its background it get a drawing brush from resource dictionary.That not a problem it works perfect …
But now I need to draw an open ended polygon on it , which gets its coordinates as an array of X and Y. let’s say intX[i] and intY[i] arrays…. So point 1 is intX[0] and intY[0] and so on ….
And after that depending on some calculation I will get some more attributes and I need to add some horizontals and vertical lines ‘no more polygons’ .
After that I need to write the result on it … so on point (x1,y1) I need to WRITE the result
Ps : is this even possible …. Is canvas a good choice ‘ I chose canvas because the coordinates should be absolute and doesn’t change when window or object is resized’ the newly drawn lines should not delete the previous drawn line or the background’
Sorry or my bad English and I hope u can guide me with something to start with….
Not sure I fully understand your need but you could use a custom ItemsControl.
First you need an entity to store all the infos for a single data-point, its position and label:
public class DataPoint : INotifyPropertyChanged
{
private double left;
public double Left
{
get { return left; }
set
{
if (value != left)
{
left = value;
PropertyChanged(this, new PropertyChangedEventArgs("Left"));
}
}
}
private double top;
public double Top
{
get { return top; }
set
{
if (value != top)
{
top = value;
PropertyChanged(this, new PropertyChangedEventArgs("Top"));
}
}
}
private string text;
public string Text
{
get { return text; }
set
{
if (value != text)
{
text = value;
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Then in your VM or directly in your code-behind you could expose it with an ObservableCollection:
public ObservableCollection<DataPoint> Points { get; set; }
Then on the view side:
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Click="GetData_Click">Get Data</Button>
<Button Click="GetText_Click">Get Text</Button>
</StackPanel>
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.Template>
<ControlTemplate>
<Canvas IsItemsHost="True" Background="AliceBlue" Width="500" Height="500"></Canvas>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataPoint}">
<Canvas>
<Ellipse Fill="Red" Width="6" Height="6" Canvas.Left="-3" Canvas.Top="-3"></Ellipse>
<TextBlock Text="{Binding Text}" Canvas.Left="10" Canvas.Top="0"></TextBlock>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DockPanel>
local is the mapping of the DataPoint class namespace.
Here are the two event handlers used for demo purposes:
Random rand = new Random();
private void GetData_Click(object sender, RoutedEventArgs e)
{
const int max = 500;
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
}
private void GetText_Click(object sender, RoutedEventArgs e)
{
foreach (DataPoint p in Points)
{
p.Text = ((char)('a' + rand.Next(26))).ToString();
}
}
Hope this helps.

WPF Drawing MultiLine Text on a canvas using the maximum font

I have several blocks of text that I need to add to a canvas for printing. They will all have a set width and height. They are all multiline and I would like the font to scale as large as possible. I have tried several things with a viewbox, however I can't seem to get the multiline and font scaling to work in unison.
<Viewbox Width="200" Height="200" Stretch="Uniform" >
<TextBox Text="Test test test test test test Test test test test test test "
TextWrapping="Wrap" AcceptsReturn="True" BorderThickness="0"></TextBox>
</Viewbox>
If I have somelike like the above, I get one line of text.
The only way I could see to do this was to start with a large font and scale it down until it fits.
double fontSize = 30;
tb.FontSize = fontSize;
while (CalculateIsTextTrimmed(tb))
{
fontSize--;
tb.FontSize = fontSize;
}
private static bool CalculateIsTextTrimmed(TextBlock textBlock)
{
Typeface typeface = new Typeface(
textBlock.FontFamily,
textBlock.FontStyle,
textBlock.FontWeight,
textBlock.FontStretch);
FormattedText formattedText = new FormattedText(
textBlock.Text,
System.Threading.Thread.CurrentThread.CurrentCulture,
textBlock.FlowDirection,
typeface,
textBlock.FontSize,
textBlock.Foreground);
formattedText.MaxTextWidth = textBlock.Width;
return (formattedText.Height > textBlock.Height);
}
Maybe a TextBlock would be better for presenting text-for-printing than a TextBox?
I tried implementing Jonah's solution. Inspired by This answer I wrapped it in a behavior (also note the addition of formattedText.Trimming = TextTrimming.None)
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace WpfApplication1
{
public class ScaleFontBehavior : Behavior<TextBlock>
{
// MaxFontSize
public static readonly DependencyProperty MaxFontSizeProperty = DependencyProperty.Register("MaxFontSize",
typeof (double), typeof (ScaleFontBehavior), new PropertyMetadata(20d));
// MinFontSize
public static readonly DependencyProperty MinFontSizeProperty = DependencyProperty.Register("MinFontSize",
typeof (double), typeof (ScaleFontBehavior), new PropertyMetadata(12d));
private readonly TextBlock dummy = new TextBlock();
public double MaxFontSize
{
get { return (double) GetValue(MaxFontSizeProperty); }
set { SetValue(MaxFontSizeProperty, value); }
}
public double MinFontSize
{
get { return (double) GetValue(MinFontSizeProperty); }
set { SetValue(MinFontSizeProperty, value); }
}
protected override void OnAttached()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty,
typeof (TextBlock));
if (dpd != null)
{
dpd.AddValueChanged(AssociatedObject, delegate { CalculateFontSize(); });
}
AssociatedObject.SizeChanged += (s, e) => CalculateFontSize();
dummy.MaxWidth = AssociatedObject.MaxWidth;
dummy.TextWrapping = AssociatedObject.TextWrapping;
}
private void CalculateFontSize()
{
double fontSize = MaxFontSize;
AssociatedObject.FontSize = fontSize;
while (CalculateIsTextTrimmed(AssociatedObject))
{
fontSize--;
if (fontSize < MinFontSize) break;
AssociatedObject.FontSize = fontSize;
}
}
private static bool CalculateIsTextTrimmed(TextBlock textBlock)
{
var typeface = new Typeface(
textBlock.FontFamily,
textBlock.FontStyle,
textBlock.FontWeight,
textBlock.FontStretch);
var formattedText = new FormattedText(
textBlock.Text,
Thread.CurrentThread.CurrentCulture,
textBlock.FlowDirection,
typeface,
textBlock.FontSize,
textBlock.Foreground)
{ Trimming = TextTrimming.None };
formattedText.MaxTextWidth = textBlock.Width;
return (formattedText.Height > textBlock.Height);
}
}
class VisualHelper
{
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
}
}
Here's is an example of it's usage (type into the TextBox and see the text in the TextBlock scale to fit.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox x:Name="TextBox" MaxLines="10" AcceptsReturn="True" />
<TextBlock Text="{Binding ElementName=TextBox, Path=Text}" Height="200" Width="200" TextWrapping="Wrap">
<i:Interaction.Behaviors>
<wpfApplication1:ScaleFontBehavior MaxFontSize="50" MinFontSize="12" />
</i:Interaction.Behaviors>
</TextBlock>
</StackPanel>
</Window>
check if this works:
<Canvas>
<Viewbox Width="200" Height="200" Stretch="Uniform" >
<StackPanel Width="200" Height="200" >
<TextBox Width="Auto" Text="Test test test test test " TextWrapping="Wrap" AcceptsReturn="True" ></TextBox>
<TextBox Width="Auto" Text="Test test test test test " TextWrapping="Wrap" AcceptsReturn="True" ></TextBox>
<TextBlock Width="Auto" Text="Test test test test test " TextWrapping="Wrap" ></TextBlock>
</StackPanel>
</Viewbox>
</Canvas>

WPF DataGrid Zoom to mouse position

I have a DataGrid that LayoutTransform is Binded to a Slider like that:
<DataGrid.LayoutTransform>
<ScaleTransform
ScaleX="{Binding ElementName=MySlider, Path=Value}"
ScaleY="{Binding ElementName=MySlider, Path=Value}" />
</DataGrid.LayoutTransform>
</DataGrid>
<Slider x:Name="MySlider"
Minimum="0.3"
Maximum="2.0"
SmallChange="0.1"
LargeChange="0.1"
Value="1.0"
IsSnapToTickEnabled="True"
TickFrequency="0.1"
TickPlacement="TopLeft"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="200"
Margin="0,0,61,0" />
<TextBlock Name="Lstate"
Text="{Binding ElementName=MySlider, Path=Value, StringFormat={}{0:P0}}"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="50" Height="20"
Margin="0,0,0,1" />
Now, in the Code I have the PreviewMouseWheel event with the following Code:
bool handle = (Keyboard.Modifiers & ModifierKeys.Control) > 0;
if (!handle)
return;
double value;
if (e.Delta > 0)
value = 0.1;
else
value = -0.1;
MySlider.Value += value;
And my question is: How to scroll to the actual Mouse Position like AutoCad or some other programs?
Thanks
Sorry for my bad english...
I have a very very good solution now:
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
EnableColumnVirtualization="False"
EnableRowVirtualization="True"
ScrollViewer.CanContentScroll="True"
private void Data_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
// Scroll to Zoom
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
// Prevent scroll
e.Handled = true;
var scrollview = FindVisualChild<ScrollViewer>(Data);
if (scrollview != null)
{
// The "+20" are there to account for the scrollbars... i think. Not perfectly accurate.
var relativeMiddle = new Point((Data.ActualWidth + 20) / 2 + (Mouse.GetPosition(Data).X - Data.ActualWidth / 2), (Data.ActualHeight + 20) / 2 + (Mouse.GetPosition(Data).Y - Data.ActualHeight / 2));
var oldLocation = Data.LayoutTransform.Transform(Data.PointFromScreen(relativeMiddle));
// Zoom
MySlider.Value += (e.Delta > 0) ? MySlider.TickFrequency : -MySlider.TickFrequency;
// Scroll
var newLocation = Data.LayoutTransform.Transform(Data.PointFromScreen(relativeMiddle));
// Calculate offset
var shift = newLocation - oldLocation;
if (scrollview.CanContentScroll)
{
// Scroll to the offset (Item)
scrollview.ScrollToVerticalOffset(scrollview.VerticalOffset + shift.Y / scrollview.ViewportHeight);
}
else
{
// Device independent Pixels
scrollview.ScrollToVerticalOffset(scrollview.VerticalOffset + shift.Y);
}
// Device independent Pixels
scrollview.ScrollToHorizontalOffset(scrollview.HorizontalOffset + shift.X);
}
}
}
It zooms to the Mouse Position on the Datagrid with and without virtualization.

wpf usercontrol positioning not updating instantly while the bound TextBox does

I have 2 TextBoxes in a Usercontrol (InfoControl) bound to a Point property in a VM implementing INotifyPropertyChanged.
I have another UserControl (DesignerControl) which contains a draggable rectangle.
The Rectangle has its Canvas.Left and Canvas.Bottom bound to a ConvPoint property of the same VM.
ConvPoint Property (0 => ActualWidth) is a converted version of the Point Property (0 => 1)
when i drag my rectangle, the ConvPoint VM Property and the values in the textboxes are instantly updated, but when i update my textboxes with new values, the VM Point Property is instantly updated but the Rectangle is positioned only when i drag the rectangle again and not instantly.
A bit of code to explain:
First, my ViewModel's Position Property
public class MyVM : ViewModelBase
{
private DependencyPoint position;
public DependencyPoint Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
}
}
public DependencyPoint ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight);}
set
{
Point p = new Point(value.X/MainVM.ActualWidth,value.Y/MainVM.ActualHeight);
this.position = p;
RaisePropertyChanged("ConvPosition");
}
}
}
Edit:
I'm using for this a class DependencyPoint to have a notification on the X and Y properties:
public class DependencyPoint : DependencyObject
{
public enum PointOrder
{
isStartPoint,
isEndPoint,
isPoint1,
isPoint2
}
public DependencyPoint()
{
}
public DependencyPoint(Double x, Double y, PointOrder po)
{
this.X = x;
this.Y = y;
this.Order = po;
}
public DependencyPoint(DependencyPoint point)
{
this.X = point.X;
this.Y = point.Y;
this.Order = point.Order;
}
public DependencyPoint(Point point, PointOrder po)
{
this.X = point.X;
this.Y = point.Y;
this.Order = po;
}
public Point ToPoint()
{
return new Point(this.X, this.Y);
}
public PointOrder Order
{
get { return (PointOrder)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
// Using a DependencyProperty as the backing store for Order. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(PointOrder), typeof(DependencyPoint), new UIPropertyMetadata(null));
public Double X
{
get { return (Double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
// Using a DependencyProperty as the backing store for X. This enables animation, styling, binding, etc...
public static readonly DependencyProperty XProperty =
DependencyProperty.Register("X", typeof(Double), typeof(DependencyPoint), new UIPropertyMetadata((double)0.0));
public Double Y
{
get { return (Double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
// Using a DependencyProperty as the backing store for Y. This enables animation, styling, binding, etc...
public static readonly DependencyProperty YProperty =
DependencyProperty.Register("Y", typeof(Double), typeof(DependencyPoint), new UIPropertyMetadata((double)0.0));
}
then in my InfoControl:
<Grid Grid.Row="0" DataContext="{Binding Position}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Margin="3,3,0,1" Grid.Row="0" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.X :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding X, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
<DockPanel Margin="3,3,0,1" Grid.Row="1" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.Y :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding Y, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
</Grid>
And in the DesignerControl:
<UserControl>
<Canvas>
<Rectangle x:Name="Point" Width="10" Height="10" Canvas.Bottom="{Binding ConvPosition.Y, Mode=TwoWay}" Canvas.Left="{Binding ConvPosition.X, Mode=TwoWay}" />
</Canvas>
</UserControl>
I have access to the actualWidth and my Rectangle is well positioned in the Canvas.
I know my properties in the VM or a bit dirty but i don't know an other way to do it properly and manage the conversion too.
any ideas?
Because ConvPosition depends from Position you should raise a NotificationChanged for ConvPosition in Position setter.
public Point Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
RaisePropertyChanged("ConvPosition");
}
}
ConvPosition you can change to this
public Point ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight); }
set
{
this.Position = new Point(value.X/MainVM.ActualWidth, value.Y/MainVM.ActualHeight);
}
}
EDIT
I think two additional properties in MyVM would be the simpliest solution.
public double X
{
get { return Position.X; }
set
{
Position = new Point(value, Position.Y);
RaisePropertyChanged("X");
}
}
public double Y
{
get { return Position.Y; }
set
{
Position = new Point(Position.X, value);
RaisePropertyChanged("Y");
}
}
Only change in Grid:
<Grid DataContext="{Binding}">

Resources