Daniel Peñalba
Daniel Peñalba

Reputation: 31857

How to draw dropshadow effect in a geometry in WPF

I'm drawing the following Shape in a Canvas.

I would like to highlight it when it's selected by changing its color (the easy part) and drawing an small halo around it:

enter image description here

This is how I did using SASS: http://codepen.io/aaromnido/pen/zKvAwd/

How coud I draw in WPF? Remember that I'm drawing using the Shape's OnRender method.

Upvotes: 1

Views: 1428

Answers (2)

AnjumSKhan
AnjumSKhan

Reputation: 9827

  1. Set some defaults in constructor.

  2. One of these defaults is Shape.Effect, as it will be animated on MouseEnter event.

  3. Construct VisualStates for Normal , and MouseEnter scenarios.

  4. Change the VisualState of the element using VisualStateManager.GoToElementState() in MouseEnter and MouseLeave event handlers.

You can expose various properties using DPs for customization.

NewShape.cs

using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Media.Effects;

namespace WpfStackOverflow.NewShape
{
    public class CNewShape : Shape
    {
        public CNewShape()
        {
            // setting the defaults
            this.Width = 40;
            this.Height = 40;
            this.Stroke = new SolidColorBrush() { Color = Colors.Red };
            this.StrokeThickness = 5;
            this.Effect = new DropShadowEffect() {

                Color = Colors.Transparent,
                BlurRadius = 1,
                Direction = -150,
                ShadowDepth = 1            
            };

            // constructing the VisualStates
            _constructVisualStates();

            // event handlers
            this.MouseEnter += CNewShape_MouseEnter;
            this.MouseLeave += CNewShape_MouseLeave;
        }

        #region EventHandlers
        void CNewShape_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            VisualStateManager.GoToElementState(this, "VSNormal", false);
        }

        void CNewShape_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            VisualStateManager.GoToElementState(this, "VSMouseEnter", false);
        }
        #endregion

        #region Overrides

        // This needs to be implemented as it is abstract in base class
        GeometryGroup geo = new GeometryGroup();
        protected override Geometry DefiningGeometry
        {
            get { return geo; }
        }

        protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
        {
            Pen pen = new Pen(this.Stroke, StrokeThickness);
            drawingContext.DrawEllipse(Brushes.Transparent, pen, new Point(Width/2, Height/2), 40, 40);

            drawingContext.DrawEllipse(Stroke, null, new Point(Width / 2, Height / 2), 30, 30);
            base.OnRender(drawingContext);
        }
        #endregion

        #region Helpers

        private void _constructVisualStates()
        {         
            VisualStateGroup vsg1 = new VisualStateGroup();

            #region VSNormal (Normal Visual State)
                VisualState stateVSNormal = new VisualState() { Name = "VSNormal" };

                Storyboard sbVSNormal = new Storyboard();
                    ObjectAnimationUsingKeyFrames oa = new ObjectAnimationUsingKeyFrames();
                    Storyboard.SetTargetProperty(oa, new PropertyPath("Effect"));
                    DiscreteObjectKeyFrame dokf = new DiscreteObjectKeyFrame(null);
                    oa.KeyFrames.Add(dokf);
                    sbVSNormal.Children.Add(oa);

                stateVSNormal.Storyboard = sbVSNormal;
                vsg1.States.Add(stateVSNormal);
            #endregion                       

            #region VSMouseEnter (MouseEnter Visual State)
                VisualState stateVSMouseEnter = new VisualState() { Name = "VSMouseEnter" };

                Storyboard sbVSMouseEnter = new Storyboard();

                    ColorAnimation caStrokeColor = new ColorAnimation();
                    caStrokeColor.To = (Color)ColorConverter.ConvertFromString("#FF24BCDE");
                    Storyboard.SetTargetProperty(caStrokeColor, new PropertyPath("(Shape.Stroke).(SolidColorBrush.Color)"));
                    sbVSMouseEnter.Children.Add(caStrokeColor);

                    ColorAnimation caEffectColor = new ColorAnimation();
                    caEffectColor.To = (Color)ColorConverter.ConvertFromString("#FFA4E1F3");
                    Storyboard.SetTargetProperty(caEffectColor, new PropertyPath("(Shape.Effect).(Color)"));
                    sbVSMouseEnter.Children.Add(caEffectColor);

                    DoubleAnimation daBlurRadius = new DoubleAnimation();
                    daBlurRadius.To = 10;
                    Storyboard.SetTargetProperty(daBlurRadius, new PropertyPath("(Shape.Effect).(BlurRadius)"));
                    sbVSMouseEnter.Children.Add(daBlurRadius);

                    DoubleAnimation daDirection = new DoubleAnimation();
                    daDirection.To = -190;
                    Storyboard.SetTargetProperty(daDirection, new PropertyPath("(Shape.Effect).(Direction)"));
                    sbVSMouseEnter.Children.Add(daDirection);              

                stateVSMouseEnter.Storyboard = sbVSMouseEnter;
                vsg1.States.Add(stateVSMouseEnter);
            #endregion

            VisualStateManager.GetVisualStateGroups(this).Add(vsg1);
        }

        #endregion
    }
}

Usage

 <local:CNewShape Canvas.Left="70" Canvas.Top="52" Stroke="#FF374095" StrokeThickness="10" Width="100" Height="100" />

Output

Quality of the image is bad. On screen actual output looks good.

Animated Button

Upvotes: 2

gomi42
gomi42

Reputation: 2519

Whatever your trigger is that your control enters the Highlighted state, in that trigger just set the Effect property. For my test the "trigger" is a property:

    public static readonly DependencyProperty ShowShadowProperty =
        DependencyProperty.Register ("ShowShadow", typeof (bool), typeof (TestShape), new PropertyMetadata (false, ShowShadowChanged));

    public bool ShowShadow
    {
        get { return (bool)GetValue (ShowShadowProperty); }
        set { SetValue (ShowShadowProperty, value); }
    }

    private static void ShowShadowChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((TestShape)d).OnShowShadow ();
    }

    private void OnShowShadow ()
    {
        if (ShowShadow)
        {
            Effect = new DropShadowEffect { Direction = 0, ShadowDepth = 20, BlurRadius = 33, Opacity = 1, Color = Colors.Black};
        }
        else
        {
            Effect = null;
        }
    }

Which means you don't need to do anything in OnRender.

Upvotes: 1

Related Questions