Is it possible to animate this UIElement in the way I want?

Emon Haque 3,176 Reputation points
2021-06-02T04:47:27.773+00:00

Here's the two chart:

101467-test.gif

Chart on the left is using class BarWithBorder : FrameworkElement and the right one using class BarWithRect : UIElement. The problem with the right one is I couldn't have rounded corners only on top and also couldn't scale up X from its center. Another problem is when I resize the window the right one animate again because of drawing OnRender, one more problem is when the Window is loaded I see bars on the right one before the animation. Here's BarWithRect :

class BarWithRect : UIElement  
{  
    SolidColorBrush brush;  
    Color normal, highlight;  
    ColorAnimation brushAnim;  
    RectAnimation rectAnim;  
    int value;  
    Rect rect;       

    public BarWithRect(int value) {  
        this.value = value;  
        normal = Colors.Gray;  
        highlight = Colors.Coral;  
        brush = new SolidColorBrush(normal);            
        brushAnim = new ColorAnimation() {  
            Duration = TimeSpan.FromSeconds(1),  
            EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }  
        };  
        rectAnim = new RectAnimation() {  
            BeginTime = TimeSpan.FromSeconds(0.75),  
            Duration = TimeSpan.FromSeconds(1),  
            EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseIn }  
        };  
    }  
    public void SetParameters(double width, double height, double upperBound) {  
        rect.Width = width - 5;  
        rect.Height = value / upperBound * height;  
        RenderSize = new Size(width, rect.Height);  
        rectAnim.From = new Rect(new Size(0, 0));  
        rectAnim.To = rect;  
        InvalidateVisual();  
    }  
    protected override void OnRender(DrawingContext dc) {  
        //dc.DrawRoundedRectangle(brush, null, rect, 10, 10);  
        dc.DrawRectangle(brush, null, rect, rectAnim.CreateClock());  
    }  
    protected override void OnMouseEnter(MouseEventArgs e) {  
        brushAnim.To = highlight;  
        brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
    }  
    protected override void OnMouseLeave(MouseEventArgs e) {  
        brushAnim.To = normal;  
        brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
    }  
}  

and here's the BarWithBorder:

class BarWithBorder : FrameworkElement  
{  
    SolidColorBrush brush;  
    Color normal, highlight;  
    Border border;  
    public double UpperBound;  
    ColorAnimation brushAnim;  
    ScaleTransform transform;  
    int value;  
    public BarWithBorder(int value) {           
        this.value = value;  
        normal = Colors.Gray;  
        highlight = Colors.Coral;  
        brush = new SolidColorBrush(normal);  
        transform = new ScaleTransform(0, 0);  
        border = new Border() {  
            Background = brush,  
            CornerRadius = new CornerRadius(0, 0, 10, 10),  
            Margin = new Thickness(5, 0, 0, 0),  
            RenderTransform = transform  
        };  
        AddVisualChild(border);  
        brushAnim = new ColorAnimation() {  
            Duration = TimeSpan.FromSeconds(1),  
            EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }  
        };  
        Loaded += animate;  
    }  
    void animate(object sender, RoutedEventArgs e) {  
        var borderScaleAnim = new DoubleAnimation() {  
            BeginTime = TimeSpan.FromSeconds(0.75),  
            To = 1,  
            Duration = TimeSpan.FromSeconds(1),  
            EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }  
        };  
        transform.BeginAnimation(ScaleTransform.ScaleXProperty, borderScaleAnim);  
        transform.BeginAnimation(ScaleTransform.ScaleYProperty, borderScaleAnim);  
    }      
    protected override Size MeasureOverride(Size availableSize) {  
        var width = availableSize.Width - border.Margin.Left;  
        transform.CenterX = width / 2;  
        border.Width = width;  
        border.Height = value / UpperBound * availableSize.Height;  
        border.Measure(availableSize);  
        return border.DesiredSize;  
    }  
    protected override Size ArrangeOverride(Size finalSize) {  
        border.Arrange(new Rect(border.DesiredSize));  
        return border.DesiredSize;  
    }  
    protected override void OnMouseEnter(MouseEventArgs e) {  
        brushAnim.To = highlight;  
        brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
    }  
    protected override void OnMouseLeave(MouseEventArgs e) {  
        brushAnim.To = normal;  
        brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
    }  
    protected override Visual GetVisualChild(int index) => border;  
    protected override int VisualChildrenCount => 1;  
}  

Both of these have been used in BarChart, here it is:

class Barchart : FrameworkElement  
{  
    double minValue, maxValue, horizontalOffset;  
    Size labelDesired;  
    VisualCollection Children;  
    public bool IsWithRect { get; set; }  
    public Barchart() {  
        Children = new VisualCollection(this);  
        ClipToBounds = true;  
        LayoutTransform = new ScaleTransform() { ScaleY = -1 };  
    }  
    void addLabel(string date) {  
        var label = new TextBlock() {  
            Tag = "Label",  
            Text = date,  
            IsHitTestVisible = false,  
            TextAlignment = TextAlignment.Right,  
            Padding = new Thickness(0, 0, 5, 0),  
            RenderTransform = new TransformGroup() {  
                Children = {  
                        new ScaleTransform() { ScaleY = -1 },  
                        new RotateTransform() { Angle = 90 }  
                    }  
            }  
        };  
        label.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));  
        if (labelDesired.Width < label.DesiredSize.Width)  
            labelDesired = label.DesiredSize;  
        Children.Add(label);  
    }  
    void addLine() {  
        var line = new Line() {  
            StrokeThickness = 1,  
            Stroke = Brushes.LightBlue,  
            IsHitTestVisible = false  
        };  
        Children.Add(line);  
        line.Loaded += animateLines;  
    }  
    void addTick(double value) {  
        var tick = new TextBlock() {  
            Text = value.ToString("N0"),  
            HorizontalAlignment = HorizontalAlignment.Right,  
            RenderTransform = new TransformGroup() {  
                Children = {  
                        new ScaleTransform() { ScaleY = - 1 },  
                        new TranslateTransform()  
                    }  
            },  
            IsHitTestVisible = false  
        };  
        Children.Add(tick);  
        tick.Loaded += animateTicks;  
    }  
    void animateLabels(object sender, RoutedEventArgs e) {  
        var label = sender as Label;  
        var anim = new DoubleAnimationUsingKeyFrames() {  
            KeyFrames = {  
                new LinearDoubleKeyFrame(0, TimeSpan.FromSeconds(0)),  
                new LinearDoubleKeyFrame(0, TimeSpan.FromSeconds(4.5)),  
                new LinearDoubleKeyFrame(90, TimeSpan.FromSeconds(5.5))  
            }  
        };  
        var transform = (label.RenderTransform as TransformGroup).Children[1];  
        transform.BeginAnimation(RotateTransform.AngleProperty, anim);  
    }  
    void animateTicks(object sender, RoutedEventArgs e) {  
        var tick = sender as TextBlock;  
        var anim = new DoubleAnimationUsingKeyFrames() {  
            KeyFrames = {  
                new LinearDoubleKeyFrame(-30 - horizontalOffset, TimeSpan.FromSeconds(0)),  
                new LinearDoubleKeyFrame(-30 - horizontalOffset, TimeSpan.FromSeconds(0.5)),  
                new LinearDoubleKeyFrame(0, TimeSpan.FromSeconds(1)),  
            }  
        };  
        var transform = (tick.RenderTransform as TransformGroup).Children[1];  
        transform.BeginAnimation(TranslateTransform.XProperty, anim);  
    }  
    void animateLines(object sender, RoutedEventArgs e) {  
        var line = sender as Line;  
        var anim = new DoubleAnimationUsingKeyFrames() {  
            KeyFrames = {  
                new LinearDoubleKeyFrame(ActualHeight - line.Y1+15, TimeSpan.FromSeconds(0)),  
                new LinearDoubleKeyFrame(ActualHeight - line.Y1+15, TimeSpan.FromSeconds(0.5)),  
                new LinearDoubleKeyFrame(0, TimeSpan.FromSeconds(1)),  
            }  
        };  
        line.RenderTransform = new TranslateTransform(0, 0);  
        line.RenderTransform.BeginAnimation(TranslateTransform.YProperty, anim);  
    }  
    protected override Size ArrangeOverride(Size arrangeSize) {  
        var testTick = new TextBlock() { Text = maxValue.ToString("N0") };  
        testTick.Measure(arrangeSize);  
        double margin = 10;  
        var labelHeight = labelDesired.Width;  
        var tickSpace = horizontalOffset = testTick.DesiredSize.Width;  
        var barSpace = tickSpace;  
        var labelSpace = tickSpace;  
        var bars = IsWithRect ? Children.OfType<BarWithRect>().Count() : Children.OfType<BarWithBorder>().Count();  
        var barWidth = (arrangeSize.Width - tickSpace - margin) / bars;  
        var lineSpace = (arrangeSize.Height - labelHeight) / 10;  
        var y = labelHeight;  
        var textY = labelHeight;  
        var upperBound = maxValue;  
        var availableHeight = (arrangeSize.Height - labelHeight) / 10 * 9;  
        foreach (UIElement item in Children) {  
            if (item is BarWithBorder || item is BarWithRect) {  
                if (IsWithRect) {  
                    var rect = item as BarWithRect;  
                    rect.SetParameters(barWidth, availableHeight, upperBound);  
                    rect.Arrange(new Rect(new Point(barSpace, labelHeight), rect.RenderSize));  
                    barSpace += rect.RenderSize.Width;  
                }  
                else {  
                    var rect = item as BarWithBorder;  
                    rect.UpperBound = upperBound;  
                    rect.Measure(new Size(barWidth, availableHeight));  
                    rect.Arrange(new Rect(new Point(barSpace, labelHeight), rect.RenderSize));  
                    barSpace += rect.DesiredSize.Width;  
                }  
            }  
            else if (item is Line) {  
                var line = item as Line;  
                line.X2 = arrangeSize.Width - margin;  
                line.Y1 = line.Y2 = y;  
                item.Measure(arrangeSize);  
                item.Arrange(new Rect(item.DesiredSize));  
                y += lineSpace;  
            }  
            else if (item is TextBlock) {  
                var block = item as TextBlock;  
                if (block.Tag != null) {  
                    block.Width = labelDesired.Width;  
                    block.Measure(arrangeSize);  
                    block.Arrange(new Rect(new Point(labelSpace, 0), block.DesiredSize));  
                    labelSpace += barWidth;  
                }  
                else {  
                    block.Measure(arrangeSize);  
                    block.Arrange(new Rect(new Point(0, textY + block.DesiredSize.Height), block.DesiredSize));  
                    textY += lineSpace;  
                }  
            }  
        }  
        return arrangeSize;  
    }  
    protected override Visual GetVisualChild(int index) => Children[index];  
    protected override int VisualChildrenCount => Children.Count;  

    public IEnumerable ItemSource {  
        get { return (IEnumerable)GetValue(ItemSourceProperty); }  
        set { SetValue(ItemSourceProperty, value); }  
    }  
    public static readonly DependencyProperty ItemSourceProperty =  
        DependencyProperty.Register("ItemSource", typeof(IEnumerable), typeof(Barchart), new FrameworkPropertyMetadata() {  
            DefaultValue = null,  
            PropertyChangedCallback = onSourceChanged,  
            AffectsArrange = true  
        });  
    static void onSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {  
        var o = d as Barchart;  
        if (e.OldValue != null) o.Children.Clear();  
        o.labelDesired = new Size(0, 0);  
        o.maxValue = 0;  
        var list = (IEnumerable<int>)e.NewValue;  
        foreach (int value in list) {  
            if(o.IsWithRect) o.Children.Add(new BarWithRect(value));  
            else o.Children.Add(new BarWithBorder(value));  
            o.addLabel(DateTime.Today.ToString("dd MMM yyyy"));  
            if (value > o.maxValue) o.maxValue = value;  
            if (value < o.minValue) o.minValue = value;  
        }  
        var min = o.minValue < 0 ? o.minValue : 0;  
        double step = 0d;  
        var current = min;  
        if (min < 0) step = (o.maxValue + Math.Abs(min)) / 9;  
        else step = o.maxValue / 9;  
        for (int i = 0; i < 10; i++) {  
            o.addLine();  
            o.addTick(current);  
            current += step;  
        }  
    }  
}  

in the MainWindow.xaml, I've these:

<Grid Margin="20">  
    <Grid.ColumnDefinitions>  
        <ColumnDefinition/>  
        <ColumnDefinition/>  
    </Grid.ColumnDefinitions>  
    <local:Barchart ItemSource="{Binding ChartValues}" Margin="10"/>  
    <local:Barchart Grid.Column="1" ItemSource="{Binding ChartValues}" IsWithRect="True" Margin="10"/>  
</Grid>  

and in MainWindow.xaml.cs, these:

public partial class MainWindow : Window  
{  
    public List<int> ChartValues { get; set; }  
    public MainWindow() {  
        InitializeComponent();  
        ChartValues = new List<int>();  
        Random rand = new();  
        for (int i = 0; i < 10; i++) {  
            ChartValues.Add(rand.Next(50, 501));  
        }  
        DataContext = this;  
    }  
}    

Can I make the right one look exactly like the left one and stop the animation when I resize window and hide that until the animation starts?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,853 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. DaisyTian-1203 11,636 Reputation points
    2021-06-03T09:20:37.907+00:00

    You can update your BarWithRect OnRender like below to stop the animation when I resize window:

     bool drawFlag = true;  
            bool aimFlag = true;  
            protected override void OnRender(DrawingContext dc)  
            {  
                if (drawFlag && aimFlag)  
                {  
                    this.Visibility = Visibility.Hidden;  
                    drawFlag = !drawFlag;  
                }  
                else if (!drawFlag && aimFlag)  
                {  
                    this.Visibility = Visibility.Visible;  
                    dc.DrawRectangle(brush, null, rect, rectAnim.CreateClock());  
                    aimFlag = !aimFlag;  
                }  
                else  
                {  
                    dc.DrawRectangle(brush, null, rect);  
                }  
            }  
    

    I will keep working on how to make hide that until the animation starts.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.