How to get rid of multiple ToolTips

Emon Haque 3,176 Reputation points
2021-05-31T07:45:42.197+00:00

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 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. Emon Haque 3,176 Reputation points
    2021-05-31T09:59:21.437+00:00

    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?


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.