Search code examples
c#wpfdata-binding

Binding not working when binding to dependency property of class (only working one way)


I am trying to bind a slider to a dependency property on a class that inherits from shape but it only seems to work one way (from class to slider.) If I set the properties via code behind it works and the slider updates.

Thanks for any ideas or help

<Window x:Class="Controls.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Controls"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <Grid>
        <StackPanel>
            <Border Width="200" Height="200">
                <local:Pie x:Name="piece1"   StartAngle="45" EndAngle="90"/>
            </Border>
            <Slider Maximum="360" Minimum="0" Value="{Binding Path=StartAngle, ElementName=piece1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></Slider>
            <Button Content="Move" Width="100" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click" />
        </StackPanel>
    </Grid>
</Grid>
</Window>

Here is the class I'm binding to. Its basically to create a kind of pie chart/slice.

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Controls
{
    public class Pie : Shape
    {

        public static readonly DependencyProperty StartAngleProperty = 
            DependencyProperty.Register("StartAngle", typeof(int), typeof(Pie), 
            new PropertyMetadata(default(int)));

        public static readonly DependencyProperty EndAngleProperty = 
            DependencyProperty.Register("EndAngle", typeof(int), typeof(Pie), 
            new PropertyMetadata(default(int)));

        public int StartAngle
        {
            get { return (int) GetValue(StartAngleProperty); }
            set
            { 
                SetValue(StartAngleProperty, value);
                InvalidateVisual();
            }
        }
        public int EndAngle
        {
            get { return (int)GetValue(EndAngleProperty); }
            set
            {
                SetValue(EndAngleProperty, value);
                InvalidateVisual();
            }
        }

        private const int CenterX = 100;
        private const int CenterY = 100;
        private const int Radius = 100;


        private double Angle
        {
            get
            {
                if (StartAngle > EndAngle)
                    return (360 - StartAngle) + EndAngle;

                return EndAngle - StartAngle;

                //return (360 - StartAngle) + EndAngle;
            }
        }

        public Pie()
        {
            Fill = (SolidColorBrush)(new BrushConverter().ConvertFrom("#4386D8"));
            Stroke = Brushes.Black;
            StrokeThickness = 1;
        }

        public static Point ComputeCartesianCoordinate(double angle, double radius)
        {
            // convert to radians
            double angleRad = (Math.PI / 180.0) * (angle - 90);

            double x = radius * Math.Cos(angleRad);
            double y = radius * Math.Sin(angleRad);

            return new Point(x, y);
        }

        protected override Geometry DefiningGeometry
        {
            get
            {
                StreamGeometry geometry = new StreamGeometry();
                geometry.FillRule = FillRule.EvenOdd;

                using (StreamGeometryContext context = geometry.Open())
                {
                    DrawGeometry(context);
                }

                geometry.Freeze();

                return geometry;
            }
        }

        private void DrawGeometry(StreamGeometryContext context)
        {
            Point startPoint = new Point(CenterX, CenterY);

            Point outerArcStartPoint = ComputeCartesianCoordinate(StartAngle, Radius);
            outerArcStartPoint.Offset(CenterX, CenterY);

            Point outerArcEndPoint = ComputeCartesianCoordinate(StartAngle + Angle, Radius);
            outerArcEndPoint.Offset(CenterX, CenterY);

            bool largeArc = Angle > 180.0;
            Size outerArcSize = new Size(Radius, Radius);

            context.BeginFigure(startPoint, true, true);
            context.LineTo(outerArcStartPoint, true, true);
            context.ArcTo(outerArcEndPoint, outerArcSize, 0, largeArc, SweepDirection.Clockwise, true, true);
        }
    }
}

Solution

  • It's actually correctly changing the value in both ways, you would notice it with a Debug.Print. Only, the setter of your property is not necessarily called by the framework when performing Binding. The issue in your code is that you're performing InvalidateVisual() in the setter of the StartAngle property, but as MSDN states here:

    In all but exceptional circumstances, your wrapper implementations should perform only the GetValue and SetValue actions, respectively. The reason for this is discussed in the topic XAML Loading and Dependency Properties.

    You should instead declare your DependencyProperty so that it automatically invalidates the visual upon update, using the FrameworkPropertyMetadata object:

    public static readonly DependencyProperty StartAngleProperty = DependencyProperty.Register(
        "StartAngle",
        typeof(int),
        typeof(Pie),
        new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsRender)
    );
    

    And leave your getter/setter like this:

    public int StartAngle {
        get { return (int)GetValue(StartAngleProperty); }
        set { SetValue(StartAngleProperty, value); }
    }
    

    Now you'll see your control redrawing when moving the slider.