OxyPlot:释放左键时保持跟踪器开启

时间:2018-03-23 14:31:06

标签: oxyplot

我正在使用OxyPlot 2014.1.546与C#和WPF。

我的情节有一个自定义跟踪器,当用户点击一个点时会出现。我想要包含用于执行与点击点相关的操作的按钮。将它们添加到跟踪器模板非常简单;问题是,默认情况下,只要用户释放鼠标按钮,跟踪器就会消失,这意味着无法实际点击它们。

有没有办法告诉OxyPlot让跟踪器保持打开状态,直到用户点击它之外?

1 个答案:

答案 0 :(得分:0)

简短的回答是,OxyPlot似乎没有直接支持这种行为。花了一些时间挖掘反编译后的源代码后,我想出了以下解决方案,这似乎有效。基本的想法是从OxyPlot的内置StayOpenTrackerManipulator派生我自己的TrackerManipulator并实例化它以响应点击。我的操纵器会覆盖虚拟Completed()函数,框架在释放鼠标按钮时会调用该函数,并将调用推迟到关闭跟踪器的基类Completed(),直到下次鼠标移动为止。单击(或直到绘图被修改,或直到鼠标离开它)。由于我使用的是C#和WPF,因此我将所有内容都包含在可以从XAML中使用的附加行为中,如下所示:

<PlotView behaviors:ShowTrackerAndLeaveOpenBehavior.BindToMouseDown="Left" />

但是如果需要的话,将内容拉出并以不同的方式重用它们将非常简单。这是来源:

/// <summary>
/// Normal OxyPlot behavior is to show the tracker when the bound mouse button is pressed,
/// and hide it again when the button is released. With this behavior set, the tracker will stay open
/// until the user clicks the plot outside it (or the plot is modified).
/// </summary>
public static class ShowTrackerAndLeaveOpenBehavior
{
    public static readonly DependencyProperty BindToMouseDownProperty = DependencyProperty.RegisterAttached(
        "BindToMouseDown", typeof(OxyMouseButton), typeof(ShowTrackerAndLeaveOpenBehavior),
        new PropertyMetadata(default(OxyMouseButton), OnBindToMouseButtonChanged));

    [AttachedPropertyBrowsableForType(typeof(IPlotView))]
    public static void SetBindToMouseDown(DependencyObject element, OxyMouseButton value) =>
        element.SetValue(BindToMouseDownProperty, value);

    [AttachedPropertyBrowsableForType(typeof(IPlotView))]
    public static OxyMouseButton GetBindToMouseDown(DependencyObject element) =>
        (OxyMouseButton) element.GetValue(BindToMouseDownProperty);

    private static void OnBindToMouseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is IPlotView plot))
            throw new InvalidOperationException($"Can only be applied to {nameof(IPlotView)}");

        if (plot.ActualModel == null)
            throw new InvalidOperationException("Plot has no model");

        var controller = plot.ActualController;
        if (controller == null)
            throw new InvalidOperationException("Plot has no controller");

        if (e.OldValue is OxyMouseButton oldButton && oldButton != OxyMouseButton.None)
            controller.UnbindMouseDown(oldButton);

        var newButton = GetBindToMouseDown(d);
        if (newButton == OxyMouseButton.None)
            return;

        controller.UnbindMouseDown(newButton);
        controller.BindMouseDown(newButton, new DelegatePlotCommand<OxyMouseDownEventArgs>(
            AddStayOpenTrackerManipulator));
    }

    private static void AddStayOpenTrackerManipulator(IPlotView view, IController controller,
        OxyMouseDownEventArgs e)
    {
        controller.AddMouseManipulator(view, new StayOpenTrackerManipulator(view), e);
    }

    private class StayOpenTrackerManipulator : TrackerManipulator
    {
        private readonly PlotModel _plotModel;
        private bool _isTrackerOpen;

        public StayOpenTrackerManipulator(IPlotView plot)
            : base(plot)
        {
            _plotModel = plot?.ActualModel ?? throw new ArgumentException("Plot has no model", nameof(plot));

            Snap = true;
            PointsOnly = false;
        }

        public override void Started(OxyMouseEventArgs e)
        {
            _plotModel.TrackerChanged += HandleTrackerChanged;
            base.Started(e);
        }

        public override void Completed(OxyMouseEventArgs e)
        {
            if (!_isTrackerOpen)
            {
                ReallyCompleted(e);
            }
            else
            {
                // Completed() is called as soon as the mouse button is released.
                // We won't call the base Completed() here since that would hide the tracker.
                // Instead, defer the call until one of the hooked events occurs.
                // The caller will still remove us from the list of active manipulators as soon as we return,
                // but that's good; otherwise the tracker would continue to move around as the mouse does.
                new DeferredCompletedCall(_plotModel, () => ReallyCompleted(e)).HookUp();
            }
        }

        private void ReallyCompleted(OxyMouseEventArgs e)
        {
            base.Completed(e);

            // Must unhook or this object will live as long as the model (instead of as long as the manipulation)
            _plotModel.TrackerChanged -= HandleTrackerChanged;
        }

        private void HandleTrackerChanged(object sender, TrackerEventArgs e) =>
            _isTrackerOpen = e.HitResult != null;

        /// <summary>
        /// Monitors events that should trigger manipulator completion and calls an injected function when they fire
        /// </summary>
        private class DeferredCompletedCall
        {
            private readonly PlotModel _plotModel;
            private readonly Action _completed;

            public DeferredCompletedCall(PlotModel plotModel, Action completed)
            {
                _plotModel = plotModel ?? throw new ArgumentNullException(nameof(plotModel));
                _completed = completed ?? throw new ArgumentNullException(nameof(completed));
            }

            /// <summary>
            /// Start monitoring events. Their observer lists will keep us alive until <see cref="Unhook"/> is called.
            /// </summary>
            public void HookUp()
            {
                Unhook();

                _plotModel.MouseDown += HandleMouseDown;
                _plotModel.Updated += HandleUpdated;
                _plotModel.MouseLeave += HandleMouseLeave;
            }

            /// <summary>
            /// Stop watching events. If they were the only things keeping us alive, we'll turn into garbage.
            /// </summary>
            private void Unhook()
            {
                _plotModel.MouseDown -= HandleMouseDown;
                _plotModel.Updated -= HandleUpdated;
                _plotModel.MouseLeave -= HandleMouseLeave;
            }

            private void CallCompletedAndUnhookEvents()
            {
                _completed();
                Unhook();
            }

            private void HandleUpdated(object sender, EventArgs e) => CallCompletedAndUnhookEvents();

            private void HandleMouseLeave(object sender, OxyMouseEventArgs e) => CallCompletedAndUnhookEvents();

            private void HandleMouseDown(object sender, OxyMouseDownEventArgs e)
            {
                CallCompletedAndUnhookEvents();

                // Since we're not setting e.Handled to true here, this click will have its regular effect in
                // addition to closing the tracker; e.g. it could open the tracker again at the new position.
                // Modify this code if that's not what you want.
            }
        }
    }
}