3
votes

Is there way to bind Point structure coordinates in XAML? For example, I want to create a triangle, which has points depended on control width and height.

<Polygon>
   <Polygon.Points>
      <Point X="{Binding ElementName=control, Path=ActualWidth}" Y="{Binding ElementName=control, Path=ActualHeight}"/>
   </Polygon.Points>
</Polygon>

Error:

A 'Binding' cannot be set on the 'X' property of type 'Point'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

I can't use inheritance, because Point type is a structure. I tried to create PointCollection property binding, but it wasn't working well.

3
a triangle with 2 points? - Mitch Wheat
No, but it is only an example of code, which causes an error. However, I declared one point. - vdrake6
Converters can help you here, you can still bind Points property of Polygon however they may not respond to change. - pushpraj
I wrote that I tried to bind Points property. Can you post an example of correct binding and converting points? - vdrake6
so how the triangle should fit in a square bound of a control? appreciated if you could post a picture to explain the same. - pushpraj

3 Answers

3
votes

Here you go

I attempted to solve the triangle with two approach

result

result


Viewbox approach

if you are interested in rendering a triangle of size of a control then this could be your choice

<Grid>
    <Viewbox Stretch="Fill">
        <Path Data="M 1.5,0 L 3,3 0,3 Z" 
              Width="3" Height="3"
              Fill="Gray" />
    </Viewbox>
</Grid>

above approach will render a triangle and will resize to the size of the parent control via Viewbox, additionally you can add a rotation transform to path and rotate the triangle as per your needs.

this approach is recommended if you simply need a triangle no matter how


Polygon using Converter

<Grid xmlns:l="clr-namespace:CSharpWPF"
      x:Name="control">
    <Grid.Resources>
        <l:ElementToTrianglePointsConverter x:Key="ElementToTrianglePointsConverter" />
    </Grid.Resources>
    <Polygon Points="{Binding ElementName=control, Converter={StaticResource ElementToTrianglePointsConverter}}"
             FillRule="Nonzero"
             Fill="Gray" />
</Grid>

converter

namespace CSharpWPF
{
    public class ElementToTrianglePointsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            FrameworkElement element = value as FrameworkElement;
            PointCollection points = new PointCollection();
            Action fillPoints = () =>
                {
                    points.Clear();
                    points.Add(new Point(element.ActualWidth / 2, 0));
                    points.Add(new Point(element.ActualWidth, element.ActualHeight));
                    points.Add(new Point(0, element.ActualHeight));
                };
            fillPoints();
            element.SizeChanged += (s, ee) => fillPoints();
            return points;// store;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

above approach is little buggy, I always have to re-size the window it before I can see the triangle. I can see it by default at design time but not at runtime unless i resize the window. although polygon have points in it but not rendered, not sure why? need to investigate the issue

2
votes

u can use MultiBinding and converter.

it may looks like this:

<Window x:Class="WpfApplication13.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local ="clr-namespace:WpfApplication13"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:converter x:Key="pointConverter" />
    </Window.Resources>
    <Grid x:Name="control" >
        <Polygon Fill="Black" Stroke="Black">
            <Polygon.Points>
                <MultiBinding  Converter="{StaticResource pointConverter}" >
                    <Binding Path="ActualWidth"  ElementName="control"/>
                    <Binding Path="ActualHeight"  ElementName="control"/>
                </MultiBinding>
            </Polygon.Points>
        </Polygon>
    </Grid>
</Window>

or you can do it in background:

public MainWindow()
        {
            InitializeComponent();
            MultiBinding mb = new MultiBinding();

            Binding bind = new Binding("ActualWidth");
            bind.Source = control;

            mb.Bindings.Add(bind);

            bind = new Binding("ActualHeight");
            bind.Source = control;

            mb.Bindings.Add(bind);
            mb.Converter = new converter();

            pg.SetBinding(Polygon.PointsProperty, mb);
        }




class converter : IMultiValueConverter
    {

        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            try
            {
                double width = (double)values[0];
                double height = (double)values[1];

                PointCollection pc = new PointCollection();
                pc.Add(new Point(0, 0));
                pc.Add(new Point(width - 10, height - 10));
                return pc;
            }
            catch
            {
                return null;
            }
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
0
votes

I have just fixed my problem. I use @pushpraj solution idea but I changed ActualWidth and ActualHeight to Width and Height so my converter class looks like that now:

namespace CSharpWPF
{
public class ElementToTrianglePointsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        FrameworkElement element = value as FrameworkElement;
        PointCollection points = new PointCollection();
        Action fillPoints = () =>
            {
                points.Clear();
                points.Add(new Point(element.Width / 2, 0));
                points.Add(new Point(element.Width, element.Height));
                points.Add(new Point(0, element.Height));
            };
        fillPoints();
        element.SizeChanged += (s, ee) => fillPoints();
        return points;// store;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
}

I think that ActualWidth and ActualHeight properties are initialized when all the children controls were initialized.

Thanks for help.