question

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

How to get rid of multiple ToolTips

Here's what it does in my Home View:

101016-test.gif

So when I select European slice, it's normal only one ToolTip pops up when the Pointer (Vertical Line) is over the Circle, for the first time it's always normal no matter which slice I select. When I select African slice, now I get two ToolTip for each point and when I select Asian slice, I get three! I'm not sure what the issue is BUT I guess it's because of the static event, Moved, in my Pointer class. Here is Pointer:

 public class Pointer : UIElement
 {
     Pen pen;
     Point start, end;
     public static event Action<double> Moved;
     public Pointer() {
         pen = new Pen(Brushes.Blue, 1) { DashStyle = DashStyles.DashDotDot, DashCap = PenLineCap.Round };
         start = new Point();
         end = new Point();
     }
     public void SetPointer(double x1, double y2, double labelWidth) {
         start.X = end.X = x1;
         start.Y = labelWidth;
         end.Y = y2;
         InvalidateVisual();
         Moved(x1);
     }
     protected override void OnRender(DrawingContext drawingContext) => drawingContext.DrawLine(pen, start, end);
 }

and it's that static event. Circle class is the subscriber, here's the Circle:

 public class Circle : FrameworkElement
 {
     public object value;
     int radius;
     Path path;
     EllipseGeometry circle;
     RadialGradientBrush radialBrush;
     GradientStop firstStop;
     DoubleAnimation gradStopAnim;
     DoubleAnimation scaleAnim;
     double leftBound, rightBound;
     HomeLineTip tip;

     public Circle(object value) {
         this.value = value;
         tip = new HomeLineTip(value);
         radius = 8;
         firstStop = new GradientStop(Colors.LightBlue, 0);
         radialBrush = new RadialGradientBrush() {
             GradientOrigin = new Point(0.5, 0.5),
             GradientStops = {
                 firstStop,
                 new GradientStop(Color.FromArgb(100, 255, 0, 0), 1)
             }
         };
         circle = new EllipseGeometry() { RadiusX = radius, RadiusY = radius };
         path = new Path() {
             Fill = radialBrush,
             Data = circle
         };
         gradStopAnim = new DoubleAnimation() {
             BeginTime = TimeSpan.FromSeconds(0.5),
             Duration = TimeSpan.FromSeconds(1),
             EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }
         };
         scaleAnim = new DoubleAnimation() {
             Duration = TimeSpan.FromSeconds(1),
             EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }
         };
         RenderTransform = new ScaleTransform();
         Loaded += appear;
         Pointer.Moved += DetectLine;
     }
     void appear(object sender, RoutedEventArgs e) {
         var anim = new DoubleAnimation() {
             BeginTime = TimeSpan.FromSeconds(2),
             Duration = TimeSpan.FromSeconds(1),
             To = 0.5,
             EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut }
         };
         RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, anim);
         RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, anim);
     }
     void animate() {
         firstStop.BeginAnimation(GradientStop.OffsetProperty, gradStopAnim);
         RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnim);
         RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnim);
     }
     void DetectLine(double x) {
         if (x != 0) {
             if (x >= leftBound && x <= rightBound) {
                 if (gradStopAnim.To != 1) {
                     gradStopAnim.To = scaleAnim.To = 1;
                     animate();
                     tip.IsOpen = true;
                 }
             }
             else {
                 if (gradStopAnim.To == 1) {
                     gradStopAnim.To = 0.25;
                     scaleAnim.To = 0.5;
                     animate();
                     tip.IsOpen = false;
                 }
             }
         }
     }
     public void SetCenter(Point center) {
         circle.Center = center;
         leftBound = center.X - radius;
         rightBound = center.X + radius;
         var transform = RenderTransform as ScaleTransform;
         transform.ScaleX = transform.ScaleY = 0;
         transform.CenterX = center.X;
         transform.CenterY = center.Y;
         InvalidateVisual();
     }
     protected override void OnRender(DrawingContext drawingContext) => drawingContext.DrawGeometry(radialBrush, null, path.Data);
 }

In the last line of Construction I've Pointer.Moved += DetectLine;. When I select a slice ItemSourceProperty of my LineChart class changes and in it's PropertyChangeCallback, onSourceChanged, I do this:

 static void onSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
     var o = d as LineChart;
     o.labelDesired = new Size(0, 0);
     if (e.OldValue != null) {
         o.Children.Clear();
     }
     o.Children.Add(new Border() { Background = Brushes.Transparent });
     var values = ((IEnumerable)e.NewValue).Cast<DepositDueRent>().ToList();
     var path = new LineStream(values.Select(x => x.Amount).ToList());
     o.Children.Add(path);
     o.maxValue = 0;
     foreach (var value in values) {
         o.Children.Add(new Circle(value));
         o.addLabel(MainVM.spaces.First(x => x.Id == value.SpaceId).Name);
         if (value.Amount > o.maxValue) o.maxValue = value.Amount;
         if (value.Amount < o.minValue) o.minValue = value.Amount;
     }
     var min = o.minValue < 0 ? o.minValue : 0;
     double step = 0d;
     var current = min;
     step = min < 0 ? ((o.maxValue) + Math.Abs(min)) / 9 : (o.maxValue) / 9;
     for (int i = 0; i < 10; i++) {
         o.addLine();
         o.addTick(current);
         current += step;
     }
     o.numPoints = values.Count;
     o.Children.Add(new Pointer());
     o.InvalidateArrange();
 }

I clear all its children with o.Children.Clear(); BUT I don't know how to unsubscribe from static event so I didn't. How to remove all handler from that static Moved event when the ItemSource changes?

windows-wpf
test.gif (1.7 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

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

Yes, that event was the issue. Now there's only one ToolTip:

101054-test.gif

BUT there's one more issue and no idea why does this happen! When I move mouse to the left fast, sometimes the leftmost Circle doesn't see that it's gone beyond its bounds, the ToolTip remains open. To get rid of that I've to move mouse slowly to the left when I get out of that detail view. Don't know any better way of removing handler from that static event other than creating a method in Circle:

 public void RemoveMoved() => Pointer.Moved -= DetectLine;

and doing this in onSourceChanged:

 if (e.OldValue != null) {
     foreach (var circle in o.Children.OfType<Circle>()) 
         circle.RemoveMoved();            
     o.Children.Clear();
 }

EDIT


Another way is Unloaded event. Now, in addition to Loaded += appear; and Pointer.Moved += detectLine; in the Circle's constructor, I've added, Unloaded += onUnloaded; and in onUnloaded I've these:

 void onUnloaded(object sender, RoutedEventArgs e) {
     Loaded -= appear;
     Pointer.Moved -= detectLine;
     Unloaded -= onUnloaded;
 }

and if I have this in Circle, I don't need the foreach in onSourceChanged and it also works. Do I have to unsubscribe from Loaded and Unloaded in the Unloaded handler or those are unnecessary?


test.gif (2.2 MiB)
· 3
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.

@EmonHaque-1485
I'm glad you solved your problem. You don't have to do that, but I pefer to use unload event to unsubscribe event, which has subscribed for the object. If you doesn't do that, you need to remove them manually.

1 Vote 1 ·

@DaisyTian-MSFT, do you unsubscribe from Loaded and Unloaded in the Unloaded eventhandler like I did:

 Loaded -= appear
 Unloaded -= onUnloaded;

or the WPF framework does that automatically on Unloaded?

0 Votes 0 ·

@EmonHaque-1485
Yes, I will unsubscribe all events which has subscribed for adding from within an element tree of loaded elements.

1 Vote 1 ·