Donut Shape in WPF

Its true that you can do a lot of very interesting things with path manipulation inside Expression Blend but there is no way to programmatically do such combine operations. The output of a Blend path combination is a new path whose points are fixed. This means that any scaling transformations are going to cause loss of fidelity of your new shape.

Luckily WPF & Silverlight have the ability to create your own custom Shape classes. Below is my code:

        private void DrawArrowGeometry(StreamGeometryContext context)
        {
            // Setup the Center Point & Radius
            Point c = new Point(ActualWidth / 2, ActualHeight / 2);
            double rOutterX = ActualWidth / 2;
            double rOutterY = ActualHeight / 2;
            double rInnerX = rOutterX - InnerWidth;
            double rInnerY = rOutterY - InnerWidth;
            
            double theta = 0;
            bool hasBegun = false;
            double x;
            double y;
            Point currentPoint;

            // Draw the Outside Edge
            for (theta = StartAngle; theta <= StopAngle; theta++)
            {
                x = c.X + rOutterX * Math.Cos(GetRadian(theta));
                y = c.Y + rOutterY * Math.Sin(GetRadian(theta));
                currentPoint = new Point(x, y);
                if (!hasBegun)
                {
                    context.BeginFigure(currentPoint, true, true);
                    hasBegun = true;
                }
                context.LineTo(currentPoint, true, true);
            }

            // Connect the Outside Edge to the Inner Edge
            x = c.X + rInnerX * Math.Cos(GetRadian(StopAngle));
            y = c.Y + rInnerY * Math.Sin(GetRadian(StopAngle));
            currentPoint = new Point(x, y);
            context.LineTo(currentPoint, true, true);

            // Draw the Inner Edge
            for (theta = StopAngle; theta >= StartAngle; theta--)
            {
                x = c.X + rInnerX * Math.Cos(GetRadian(theta));
                y = c.Y + rInnerY * Math.Sin(GetRadian(theta));
                currentPoint = new Point(x, y);
                context.LineTo(currentPoint, true, true);
            }

            // Connect the Inner Edge to the Outside Edge
            x = c.X + rOutterX * Math.Cos(GetRadian(StartAngle));
            y = c.Y + rOutterY * Math.Sin(GetRadian(StartAngle));
            currentPoint = new Point(x, y);
            context.LineTo(currentPoint, true, true);

            context.Close();
        }

 

After adding dependency properties for Start Angle, StopAngle, and InnerWidth. Here is the result:

01

        <local:Donut Width="100" Stroke="Black" StrokeThickness="2" HorizontalAlignment="Left" Margin="18,106,0,58" Height="100" InnerWidth="35" StopAngle="180" >
            <local:Donut.Fill>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF000000" Offset="0"/>
                    <GradientStop Color="#FFCC1F1F" Offset="1"/>
                </LinearGradientBrush>
            </local:Donut.Fill>
        </local:Donut>
        <local:Donut StrokeThickness="2" Margin="0,42,41,122" Height="100" HorizontalAlignment="Right" Width="100" InnerWidth="10" d:LayoutOverrides="Height" >
            <local:Donut.Fill>
                <RadialGradientBrush>
                    <GradientStop Color="#FFFFFFFF" Offset="1"/>
                    <GradientStop Color="#FFFF7400" Offset="0.71"/>
                </RadialGradientBrush>
            </local:Donut.Fill>
        </local:Donut>
        <local:Donut StrokeThickness="2" Margin="0,0,41,8" Height="100" HorizontalAlignment="Right" Width="100" InnerWidth="10" VerticalAlignment="Bottom" d:LayoutOverrides="Height" StopAngle="225" Stroke="#FF000000" >
            <local:Donut.Fill>
                <RadialGradientBrush>
                    <GradientStop Color="#FFFFFFFF" Offset="1"/>
                    <GradientStop Color="#FFFF0000" Offset="0.536"/>
                </RadialGradientBrush>
            </local:Donut.Fill>
        </local:Donut>

 

UPDATE:

I reconstructed the project and posted the code to GitHub. There appears to be a bug in the reconstructed code such that as you resize the window it will continuously get bigger.

8 thoughts on “Donut Shape in WPF

  1. For those of you that didn’t know what the GetRadian implementation was: private double GetRadian(double angle) { return (Math.PI / 180.0) * (angle – 90); }

    Like

  2. I didn’t want a line on the wedge if the wedge was a circle or a ring …So didn’t stroke the line if my angle was 360 …context.LineTo(currentPoint, Angle != 360, true);

    Like

  3. The implementation presented is a bit different then mine …I have a rotation angle, and wedge angle …So start angle translates to rotation angle …stop angle translates to rotation angle + wedge angle …I wrapped wedge angle to do some checking …public double Angle { get { double rc = WedgeAngle; if (WedgeAngle > 360) { rc = WedgeAngle % 360; if (rc == 0) { rc = 360; } } return rc; } }

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s