Tim
Tim

Reputation: 15237

Databound Triangle Creation?

I've got a requirement to create several shapes based on a supplied size (all of them have the same height/width) and have their sizes be databound to that supplied property on the datacontext.

Most of the shapes are easy: Circle (ellipse with height/width bound), square (rectangle with height/width bound), diamond (same as square, then use a RotateTransform), + (two lines), X (two lines).

But I'm trying to figure out how to do it for a triangle and I can't figure it out. It needs to be a filled object, so I can't just do it with three lines.

But all of the ways i've seen to do it (w/ a Path or a Polygon) end up taking Point objects (StartPoint, EndPoint, etc). And you can't bind to the X or Y values of the Point object.

Am I missing something? Or do I need to write my own custom shape or something?

Edit: To add a little bit of clarity... the type of triangle I'm creating doesn't really matter. It can be equilateral or isosceles. I was targeting an isosceles, so that it would have a base with the databound width and the top "tip" of the triangle will be at the mid-point of the databound width and at Y=0. That was just an optimization for simplicity's sake

Upvotes: 2

Views: 370

Answers (2)

user572559
user572559

Reputation:

The behavior class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;

namespace WpfApplication1
{
    public enum ShapeType
    {
        Rectangle,
        Isosceles,
        Ellipse,
        Dice,
        Hexagon
    }

    public class PathControl
    {
        public static readonly DependencyProperty ShapeTypeProperty =
            DependencyProperty.RegisterAttached("ShapeType",
            typeof(ShapeType?),
            typeof(DependencyObject),
            new PropertyMetadata(null, 
                new PropertyChangedCallback((sender, args) => 
                { 
                    Path path = sender as Path;
                    ShapeType? shapeType = (ShapeType?)args.NewValue;

                    //todo: use a WeakEvent
                    path.SizeChanged += 
                        (pathSender, pathArgs) => 
                        {
                            PathControl.InvalidatePath((Path)sender, shapeType);
                        };

                })));


        private static void InvalidatePath(Path path, ShapeType? shapeType)
        {
            if (path != null
                && shapeType.HasValue)
            {
                string source = null;

                double netWidth = path.Width - 2 * path.StrokeThickness,
                       netHeight = path.Height - 2 * path.StrokeThickness;

                if (shapeType == ShapeType.Rectangle)
                {
                    source = string.Format("M0,0 h{0} v{1} h-{0} z",
                        new object[2]
                        {
                            netWidth,
                            netHeight
                        });
                }
                else if (shapeType == ShapeType.Isosceles)
                {
                    source = string.Format("M0,{1} l{0},-{1} {0},{1} z",
                        new object[2]
                        {
                            netWidth / 2,
                            netHeight
                        });
                }
                else
                {
                    throw new NotImplementedException(shapeType.ToString());
                }

                path.Data = Geometry.Parse(source);
            }
        }

        public static void SetShapeType(DependencyObject o, ShapeType e)
        {
            o.SetValue(PathControl.ShapeTypeProperty, e);
        }

        public static ShapeType? GetShapeType(DependencyObject o)
        {
            return (ShapeType)o.GetValue(PathControl.ShapeTypeProperty);
        }
    }
}

The XAML:

<Window x:Class="WpfApplication1.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:local="clr-namespace:WpfApplication1">
    <Grid>
        <Path Width="100" Height="100" Stroke="Green" StrokeThickness="2" Fill="Yellow"
              local:PathControl.ShapeType="Isosceles">
            <Path.RenderTransform>
                <RotateTransform Angle="90"></RotateTransform>
            </Path.RenderTransform>
        </Path>
    </Grid>
</Window>

Upvotes: 2

Emond
Emond

Reputation: 50692

Binding to the Points is the best/only way. The X and X properties of a Point cannot be bound to because they do not raise the PropertyChanged event. The Point is a structure and structures should be read-only.

The PointCollection class raises the correct events so you can bind to it. This allows you to manipulate the triangles by modifying the collection of point by replacing the points. Do not change the point but replace them so the proper events will be raised.

Upvotes: 2

Related Questions