question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked EmonHaque-1485 commented

What's the difference between Panel and FrameworkElement

I was simplifying the code of the PieChart I'd created once I restarted experimenting on WPF after spending a month on Android API and they've almost similar architecture of WPF. At first, I started with Canvas and then Panel, FrameworkElement, UIElement, DrawingVisual and Visual. After all that little experiment I've decided to use FrameworkElement since it gives me Loaded event and DependencyProperty binding for free. The PieChart was created before I got into FrameworkElement so it was with Panel. Today, I've simplified it to this:

 public class PieChart : Panel
 {
     TextBlock infoBlock;
     Run value, percentage;
     Path ellipse;
     DoubleAnimation ellipseAnim, infoAnim;
     ScaleTransform infoScale, ellipseScale;
     int total;
     public PieChart() {
         Margin = new Thickness(10);
         value = new Run() { FontSize = 32, FontWeight = FontWeights.Bold, Foreground = Brushes.Gray };
         percentage = new Run() { Foreground = Brushes.Blue };
         infoScale = new ScaleTransform(0, 0);
         ellipseScale = new ScaleTransform(0, 0);
         infoBlock = new TextBlock() {
             TextAlignment = TextAlignment.Center,
             Inlines = { value, new LineBreak(), percentage },
             RenderTransform = infoScale
         };
         ellipse = new Path() {
             Fill = Brushes.White,
             Data = new EllipseGeometry(),
             RenderTransform = ellipseScale
         };
         ellipseAnim = new DoubleAnimation() {
             BeginTime = TimeSpan.FromSeconds(1),
             Duration = TimeSpan.FromSeconds(1),
             EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }
         };
         infoAnim = new DoubleAnimation() {
             Duration = TimeSpan.FromSeconds(0.5),
             EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }
         };
     }
     protected override Size ArrangeOverride(Size finalSize) {
         var list = ItemsSource.Cast<int>().ToList();
         double cx = finalSize.Width / 2;
         double cy = finalSize.Height / 2;
         double radius, startAngle, sweepAngle, endAngle;
         startAngle = sweepAngle = endAngle = 0d;
         radius = cx > cy ? cy : cx;
         int index = 0;
         foreach (PieSlice item in Children.OfType<PieSlice>()) {
             var value = list[index];
             startAngle += sweepAngle;
             sweepAngle = 2 * Math.PI * value / total;
             endAngle = startAngle + sweepAngle;
             bool isLarge = (double)value / total > 0.5;
             item.SetParameters(cx, cy, radius, startAngle, sweepAngle, isLarge);
             item.Arrange(new Rect(item.DesiredSize));
             index++;
         }
         var geo = (EllipseGeometry)ellipse.Data;
         geo.RadiusX = geo.RadiusY = radius - 100;
         geo.Center = new Point(cx, cy);
         ellipse.Measure(finalSize);
         ellipse.Arrange(new Rect(ellipse.DesiredSize));
         ellipseScale.CenterX = cx;
         ellipseScale.CenterY = cy;
         infoBlock.Measure(finalSize);
         infoScale.CenterX = infoBlock.DesiredSize.Width / 2;
         infoScale.CenterY = infoBlock.DesiredSize.Height / 2;
         infoBlock.Arrange(new Rect(new Point(cx - infoScale.CenterX, cy - infoScale.CenterY), infoBlock.DesiredSize));
         return finalSize;
     }
     protected override void OnMouseEnter(MouseEventArgs e) {
         var point = e.GetPosition(this);
         var result = VisualTreeHelper.HitTest(this, point);
         if(result.VisualHit is PieSlice) {
             double val = ((PieSlice)result.VisualHit).value;
             value.Text = val.ToString();
             percentage.Text = (val / total * 100).ToString("N2") + "%";
             animate(1);
         }
     }
     protected override void OnMouseLeave(MouseEventArgs e) => animate(0);   
     void animate(double scale) {
         if (scale == 1) infoAnim.BeginTime = TimeSpan.FromSeconds(1.5);
         else infoAnim.BeginTime = TimeSpan.FromSeconds(1);
         ellipseAnim.To = infoAnim.To = scale;
         ellipse.RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, ellipseAnim);
         ellipse.RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, ellipseAnim);
         infoScale.BeginAnimation(ScaleTransform.ScaleXProperty, infoAnim);
         infoScale.BeginAnimation(ScaleTransform.ScaleYProperty, infoAnim);
     }
     public IEnumerable ItemsSource {
         get { return (IEnumerable)GetValue(ItemsSourceProperty); }
         set { SetValue(ItemsSourceProperty, value); }
     }
     public static readonly DependencyProperty ItemsSourceProperty =
         DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(PieChart), new PropertyMetadata(null, onSourceChanged));
     static void onSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
         var o = d as PieChart;
         if (e.OldValue != null) o.Children.Clear();
         var items = (IEnumerable)e.NewValue;
         Random rand = new();
         foreach (int item in items) {
             o.total += item;
             var color = Color.FromRgb((byte)rand.Next(0, 256), (byte)rand.Next(0, 256), (byte)rand.Next(0, 256));
             var slice = new PieSlice(new SolidColorBrush(color), item);
             o.Children.Add(slice);
         }
         o.Children.Add(o.ellipse);
         o.Children.Add(o.infoBlock);
     }
 }

Now, I can easily convert it as a FrameworkElement, all I've to do is:

1) change the first line to public class PieChart : FrameworkElement
2) add a VisualCollection field and initialize that in constructor like this:

 VisualCollection Children;
 public PieChart() {
     Children = new VisualCollection(this);
     ...
 }

3) override these two methods this way:

 protected override Visual GetVisualChild(int index) => Children[index];
 protected override int VisualChildrenCount => Children.Count;  

No other changes is required and it works fine both with Panel and FrameworkElement:

100757-test.gif

What's the difference? Will there be any performance difference if I choose one over the other?

EDIT


One more question, previously, I'd:

 slice.MouseEnter += o.showPopup;
 slice.MouseLeave += o.hidePopup;

in onSourceChanged so the info changed when I move mouse from one slice to the other. Now it doesn't unless there's a gap or I take mouse away from this. Wanted to use Preview event where I can check e.Source BUT looks like there is no Preview for MouseEnter/Leave. Is there any other PreviewEvent which I can use to do that?

windows-wpf
test.gif (895.1 KiB)
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 EmonHaque-1485 commented

Panel Inheritances from FrameworkElement and Implements IAddChild, please check Panel reference source and FrameworkElement reference source. GetVisualChild and VisualChildrenCount exist in both Panel and FrameworkElement, Panel should override they in the frameworkElement. You can compare the two documents to see how they differ.

There is no Preview for MouseEnter/Leave. The Routing strategy for MouseEnter and MouseLeave is Direct, which you need is Tunneling. For more details, you can refer to Routing Strategies. Does PreviewMouseDown/PreviewMouseUp meet your requirement?


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
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.

@DaisyTian-MSFT, converted that as FrameworkElement, almost all of my custom controls are now FrameworkElement, it'll be lighter since it's parent. I already have used PreviewMouseUp for showing LineChart in another view.

0 Votes 0 ·