question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked DaisyTian-1203 answered

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

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-wpf
test.gif (1.0 MiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

DaisyTian-1203 avatar image
1 Vote"
DaisyTian-1203 answered

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.


5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.