none
MS Chartの再描画で固まる RRS feed

  • 質問

  • 以下のようなコードでスケールを繰り返していると、数十秒間応答が無くなってしまい操作出来なくなる場合があります。

    おそらく再描画に時間がかかってしまっているのではないかと予想しているのですが、応答が無くならないような回避策はありますでしょうか。

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.DataVisualization.Charting;
    
    namespace ChartSample
    {
        public partial class Form1 : Form
        {
            private bool isMouseDown;
            private bool isMouseUp;
    
            private Point pointBefore;
            private Point pointAfter;
    
            public Form1()
            {
                InitializeComponent();
    
                Chart chart = new Chart();
                chart.ChartAreas.Add(new ChartArea());
    
                chart.ChartAreas[0].AxisX.LabelStyle.Format = "0.00";
                chart.ChartAreas[0].AxisY.LabelStyle.Format = "0.00";
    
                chart.Series.Add(new Series());
                chart.Series.Add(new Series());
                chart.Series[0].ChartType = SeriesChartType.FastLine;
                chart.Series[1].ChartType = SeriesChartType.FastLine;
                chart.Dock = DockStyle.Fill;
    
                chart.Series[0].ToolTip = "x : #VALX\n#VALY";
    
                chart.MouseDown += chart_MouseDown;
                chart.MouseUp += chart_MouseUp;
                chart.MouseMove += chart_MouseMove;
                chart.PostPaint += chart_PostPaint;
                this.Controls.Add(chart);
    
                Random r = new Random();
                for (int x = 0; x < 50000; x++)
                {
                    chart.Series[0].Points.Add(new DataPoint(x, r.NextDouble()));
                }
                for (int x = 0; x < 50000; x++)
                {
                    chart.Series[1].Points.Add(new DataPoint(x, r.NextDouble()));
                }
            }
    
            private void chart_MouseDown(object sender, MouseEventArgs e)
            {
                Chart chart = (Chart)sender;
    
                if (e.Button == MouseButtons.Right)
                {
                    this.isMouseDown = true;
                    this.isMouseUp = false;
                    this.pointBefore = e.Location;
                }
            }
    
            private void chart_MouseUp(object sender, MouseEventArgs e)
            {
                Chart chart = (Chart)sender;
                ChartArea area = chart.ChartAreas[0];
    
                if (e.Button == MouseButtons.Right)
                {
                    this.pointAfter = e.Location;
    
                    if ((this.isMouseUp == true) &&
                        (this.pointBefore.X != this.pointAfter.X) &&
                        (this.pointBefore.Y != this.pointAfter.Y))
                    {
                        double bx = chartAreaPixelPositionToValue(area.AxisX, chart.Width, this.pointBefore.X);
                        double by = chartAreaPixelPositionToValue(area.AxisY, chart.Height, this.pointBefore.Y);
                        double ax = chartAreaPixelPositionToValue(area.AxisX, chart.Width, this.pointAfter.X);
                        double ay = chartAreaPixelPositionToValue(area.AxisY, chart.Height, this.pointAfter.Y);
    
                        area.AxisX.ScaleView.Zoom(Math.Min(bx, ax), Math.Max(bx, ax));
                        area.AxisY.ScaleView.Zoom(Math.Min(by, ay), Math.Max(by, ay));
    
                        this.isMouseDown = false;
                    }
                }
            }
    
            private void chart_MouseMove(object sender, MouseEventArgs e)
            {
                Chart chart = (Chart)sender;
    
                if (e.Button == MouseButtons.Right)
                {
                    this.pointAfter = e.Location;
                    chart.Invalidate();
                    this.isMouseUp = true;
                }
            }
    
            private void chart_PostPaint(object sender, ChartPaintEventArgs e)
            {
                Chart chart = (Chart)sender;
    
                if ((e.ChartElement == chart) &&
                    (this.isMouseDown == true))
                {
                    float x = Math.Min(this.pointBefore.X, this.pointAfter.X);
                    float y = Math.Min(this.pointBefore.Y, this.pointAfter.Y);
                    float w = Math.Abs(this.pointBefore.X - this.pointAfter.X);
                    float h = Math.Abs(this.pointBefore.Y - this.pointAfter.Y);
    
                    Pen pen = new Pen(Color.Red, 3);
                    pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                    e.ChartGraphics.Graphics.DrawRectangle(pen, x, y, w, h);
                }
            }
    
            private double chartAreaPixelPositionToValue(Axis axis, int range, int point)
            {
                double ret = 0;
    
                if (0 > point)
                    point = 0;
    
                if (range < point)
                    point = range -1;
    
                ret = axis.PixelPositionToValue(point);
    
                return ret;
            }
        }
    }


    2017年2月24日 23:56

回答

  • なんだかChartがMouseMoveで行っている処理がかなり拡大した状態だとやたら時間がかかってるので、ChartのMouseMoveを無効にしてみる

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.DataVisualization.Charting;
    
    namespace ChartSample
    {
        public partial class Form1 : Form
        {
            private bool isMouseDown;
            private bool isMouseUp;
    
            private Point pointBefore;
            private Point pointAfter;
    
            public Form1()
            {
                InitializeComponent();
    
                ChartEx chart = new ChartEx();//継承したChartを使用
    
    
                chart.ChartAreas.Add(new ChartArea());
    
                chart.ChartAreas[0].AxisX.LabelStyle.Format = "0.00";
                chart.ChartAreas[0].AxisY.LabelStyle.Format = "0.00";
                chart.ChartAreas[0].AxisX2.LabelStyle.Format = "0.00";
                chart.ChartAreas[0].AxisY2.LabelStyle.Format = "0.00";
    
                chart.Series.Add(new Series());
                chart.Series.Add(new Series());
                chart.Series[0].ChartType = SeriesChartType.FastLine;
                chart.Series[1].ChartType = SeriesChartType.FastLine;
    
                chart.Series[1].XAxisType = AxisType.Secondary;
                chart.Series[1].YAxisType = AxisType.Secondary;
    
                chart.Dock = DockStyle.Fill;
    
                chart.Series[0].ToolTip = "x : #VALX\n#VALY";
    
                chart.MouseDown += chart_MouseDown;
                chart.MouseUp += chart_MouseUp;
                chart.PreMouseMove += chart_PreMouseMove;//元のMouseMoveイベントは使わない
                chart.PostPaint += chart_PostPaint;
    
                this.Controls.Add(chart);
    
                Random r = new Random();
                for (int x = 0; x < 50000; x += 1)
                {
                    chart.Series[0].Points.Add(new DataPoint(x, x & 1));
                }
                for (int x = 0; x < 50000; x += 1)
                {
                    chart.Series[1].Points.Add(new DataPoint(x, (x + 1) & 1));
                }
                var area = chart.ChartAreas[0];
    
                this.ClientSize = new Size(300, 300);
                area.AxisX.ScaleView.Zoom(40000.1473754173, 40000.8874906683);
                area.AxisY.ScaleView.Zoom(0.799297314829825, 0.799311445164638);
    
                area.AxisX2.ScaleView.Zoom(40000.1473754173, 40000.8874906683);
                area.AxisY2.ScaleView.Zoom(0.799297314829825, 0.799311445164638);
            }
    
            #region マウスによる拡大処理
            private void chart_MouseDown(object sender, MouseEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
    
                if (e.Button == MouseButtons.Right)
                {
                    this.isMouseDown = true;
                    this.isMouseUp = false;
                    this.pointBefore = e.Location;
                }
            }
    
            private void chart_MouseUp(object sender, MouseEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
                ChartArea area = chart.ChartAreas[0];
    
                if (e.Button == MouseButtons.Right)
                {
                    Axis axisX;
                    Axis axisY;
                    if (ModifierKeys == Keys.Shift)
                    {
                        axisX = area.AxisX2;
                        axisY = area.AxisY2;
                    }
                    else
                    {
                        axisX = area.AxisX;
                        axisY = area.AxisY;
                    }
    
                    this.pointAfter = e.Location;
    
                    if ((this.isMouseUp == true) &&
                        (this.pointBefore.X != this.pointAfter.X) &&
                        (this.pointBefore.Y != this.pointAfter.Y))
                    {
                        double bx = chartAreaPixelPositionToValue(axisX, chart.Width, this.pointBefore.X);
                        double by = chartAreaPixelPositionToValue(axisY, chart.Height, this.pointBefore.Y);
                        double ax = chartAreaPixelPositionToValue(axisX, chart.Width, this.pointAfter.X);
                        double ay = chartAreaPixelPositionToValue(axisY, chart.Height, this.pointAfter.Y);
                        axisX.ScaleView.Zoom(Math.Min(bx, ax), Math.Max(bx, ax));
                        axisY.ScaleView.Zoom(Math.Min(by, ay), Math.Max(by, ay));
                        this.isMouseDown = false;
                    }
                }
            }
    
            private void chart_PreMouseMove(object sender, PreMouseEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
                if (e.Button == MouseButtons.Right)
                {
                    this.pointAfter = e.Location;
                    chart.Invalidate();
                    this.isMouseUp = true;
                }
                e.Cancel = true;
            }
    
            private void chart_PostPaint(object sender, ChartPaintEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
    
                if ((e.ChartElement == chart) &&
                    (this.isMouseDown == true))
                {
                    float x = Math.Min(this.pointBefore.X, this.pointAfter.X);
                    float y = Math.Min(this.pointBefore.Y, this.pointAfter.Y);
                    float w = Math.Abs(this.pointBefore.X - this.pointAfter.X);
                    float h = Math.Abs(this.pointBefore.Y - this.pointAfter.Y);
    
                    Pen pen = new Pen(Color.Red, 3);
                    pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                    e.ChartGraphics.Graphics.DrawRectangle(pen, x, y, w, h);
                }
            }
    
            private double chartAreaPixelPositionToValue(Axis axis, int range, int point)
            {
                double ret = 0;
    
                if (0 > point)
                    point = 0;
    
                if (range < point)
                    point = range - 1;
    
                ret = axis.PixelPositionToValue(point);
    
                return ret;
            }
    
            #endregion
        }
    
    
        /// <summary>ズームの不具合を対策してみたChart</summary>
        class ChartEx : System.Windows.Forms.DataVisualization.Charting.Chart
        {
            private ChartScrollWorker scrollWorker;
    
            public ChartEx()
            {
                scrollWorker = new ChartScrollWorker(this);
            }
    
            #region Chartの本来のMouseMoveを無効化するための処理
    
            protected override void WndProc(ref Message m)
            {
                DateTime now = DateTime.Now;
                try
                {
                    base.WndProc(ref m);
    
                    var span = DateTime.Now - now;
                    //if (span.TotalSeconds > 5)
                    //{
                    //    System.Diagnostics.Debugger.Break();
                    //}
                }
                catch (System.OverflowException)
                {
                    MessageBox.Show("たぶんGDI+の描画の限界");
                }
                catch (System.Runtime.InteropServices.ExternalException)
                {
                    MessageBox.Show("たぶんGDI+の描画の限界");
                }
            }
    
            /// <summary>ChartがMouseMoveイベントを処理する前のイベント</summary>
            public event EventHandler<PreMouseEventArgs> PreMouseMove;
    
            protected override void OnMouseMove(MouseEventArgs e)
            {
                PreMouseEventArgs pm = new PreMouseEventArgs(e);
                if (PreMouseMove != null)
                {
                    PreMouseMove(this, pm);
                    if (pm.Cancel)
                    {
                        return;
                    }
                }
    
                base.OnMouseMove(e);
            }
            #endregion
        }
        /// <summary>ChartのScrollBarのマウスでのスクロール処理を置き換えるクラス</summary>
        class ChartScrollWorker
        {
            public ChartScrollWorker(ChartEx chart)
            {
                this.chart = chart;
                this.chart.AxisScrollBarClicked += Chart_AxisScrollBarClicked;
            }
            ChartEx chart;
            Axis targetAxis;
            double scrollBarPosition;
            double scrollBarLength;
            double viewLength;
            bool isHorizontal;
    
            void Chart_AxisScrollBarClicked(object sender, ScrollBarEventArgs e)
            {
    
                if (e.ButtonType == ScrollBarButtonType.ThumbTracker)
                {
                    //Chartのスクロールバーのツマミの場合
                    e.IsHandled = true;//元のMouseMoveを無効にする
    
                    this.targetAxis = e.Axis;
    
                    var area = targetAxis.ScrollBar.ChartArea;
                    var scrollBar = targetAxis.ScrollBar;
                    var size = scrollBar.Size;
                    var p1 = targetAxis.ValueToPixelPosition(targetAxis.ScaleView.ViewMinimum);
                    var p2 = targetAxis.ValueToPixelPosition(targetAxis.ScaleView.ViewMaximum);
                    Axis axisCross;
                    this.isHorizontal = area.AxisX == targetAxis || area.AxisX2 == targetAxis;
    
                    int buttonCount;
                    if (isHorizontal)
                    {
                        axisCross = area.AxisY;
                        buttonCount = 1;
                    }
                    else
                    {
                        axisCross = area.AxisX;
                        buttonCount = 0;
                    }
    
                    double legnth = Math.Abs(p1 - p2);
                    double pos = Math.Min(p1, p2);
                    double width = size;
                    if (axisCross.ScrollBar.IsVisible)
                    {
                        pos = pos + axisCross.ScrollBar.Size;
                        legnth = legnth - axisCross.ScrollBar.Size;
                    }
                    switch (scrollBar.ButtonStyle)
                    {
                        case ScrollBarButtonStyles.All:
                            pos = pos + scrollBar.Size * (buttonCount + 1);
                            legnth = legnth - scrollBar.Size * 2;
                            break;
                        case ScrollBarButtonStyles.ResetZoom:
                            pos = pos + scrollBar.Size * buttonCount;
                            legnth = legnth - scrollBar.Size * 1;
                            break;
                        case ScrollBarButtonStyles.SmallScroll:
                            pos = pos + scrollBar.Size * buttonCount;
                            legnth = legnth - scrollBar.Size * 2;
                            break;
                    }
    
                    //スクロールバーのスクロール可能範囲を登録
                    this.scrollBarPosition = pos;
                    this.scrollBarLength = legnth;
                    this.viewLength = targetAxis.ScaleView.ViewMaximum - targetAxis.ScaleView.ViewMinimum;
    
                    //マウスイベントをキャプチャ
                    this.chart.Capture = true;
                    this.chart.PreMouseMove += Chart_PreMouseMove;//元のMouseMoveより前に処理
                    this.chart.MouseUp += Chart_MouseUp;
                }
            }
    
            void Chart_PreMouseMove(object sender, PreMouseEventArgs e)
            {
                //スクロールバーのマウス位置に出来るだけあうようにスクロール
                var area = targetAxis.ScrollBar.ChartArea;
                double per;
                if (isHorizontal)
                {
                    per = Math.Min(Math.Max(0, (e.Location.X - scrollBarPosition) / scrollBarLength), 1);
                }
                else
                {
                    per = 1 - Math.Min(Math.Max(0, (e.Location.Y - scrollBarPosition) / scrollBarLength), 1);
                }
    
                double viewValue = (targetAxis.Maximum - targetAxis.Minimum) * per + targetAxis.Minimum;
                viewValue = Math.Min(viewValue, targetAxis.Maximum - viewLength);
                targetAxis.ScaleView.Zoom(viewValue, viewValue + viewLength);
    
                //e.Cancel = true;
            }
    
            void Chart_MouseUp(object sender, MouseEventArgs e)
            {
                chart.Capture = false;
                chart.PreMouseMove -= Chart_PreMouseMove;
                chart.MouseUp -= Chart_MouseUp;
            }
        }
    
        class PreMouseEventArgs : MouseEventArgs
        {
            public PreMouseEventArgs(MouseEventArgs e)
                : base(e.Button, e.Clicks, e.X, e.Y, e.Delta)
            {
            }
            public bool Cancel { get; set; }
        }
    }
    #細かくなりすぎてGDI+の描画できる限界なのかな?

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年2月26日 3:19
  • Tooltipが問題とわかっているなら自前でTooltip出せばいいだけのような…

    /// <summary>ズームの不具合を対策してみたChart</summary>
    class ChartEx : System.Windows.Forms.DataVisualization.Charting.Chart
    {
        private ChartScrollWorker scrollWorker;
        private ToolTip tooltip;
        private bool tooltipVisible;
        private Point tooltioPos;
        public ChartEx()
        {
            scrollWorker = new ChartScrollWorker(this);
            tooltip = new ToolTip();
            Application.Idle += Application_Idle;
        }
    
        void Application_Idle(object sender, EventArgs e)
        {
            var pos = System.Windows.Forms.Cursor.Position;
            pos = this.PointToClient(pos);
            if (tooltipVisible)
            {
                return;
            }
            if (0 <= pos.X && pos.X < this.Width && 0 <= pos.Y && pos.Y < this.Height)
            {
                double fx = (double)pos.X / this.Width * 100;
                double fy = (double)pos.Y / this.Height * 100;
                foreach (ChartArea area in this.ChartAreas)
                {
    
                    if (area.InnerPlotPosition.X <= fx && fx <= area.InnerPlotPosition.Right)
                    {
                        if (area.InnerPlotPosition.Y <= fy && fy <= area.InnerPlotPosition.Bottom)
                        {
                            var points = this.Series[0].Points;
                            var x = area.AxisX.PixelPositionToValue(pos.X);
                            double per = x / (area.AxisX.Maximum - area.AxisX.Minimum);
                            int index = (int)Math.Floor(points.Count * per);
                            index = Math.Min(Math.Max(0, index), points.Count - 1);
                            var p = points[index];
                            var size=System.Windows.Forms.Cursor.Current.Size;
                                
                            pos.Offset(size.Width/2,0);
    
                            tooltip.Show(p.ToString(), this,  pos) ;
                            tooltioPos = System.Windows.Forms.Cursor.Position;
                            tooltipVisible = true;
                            System.Diagnostics.Debug.WriteLine("SHOW");
                            break;
                        }
                    }
                }
            }
        }
    
        #region Chartの本来のMouseMoveを無効化するための処理
    
        protected override void WndProc(ref Message m)
        {
            DateTime now = DateTime.Now;
            try
            {
                if (m.Msg == 0x200)
                {
                    System.Diagnostics.Debug.WriteLine("WM_MOUSEMOVE");
                }
                base.WndProc(ref m);
    
                var span = DateTime.Now - now;
                //if (span.TotalSeconds > 5)
                //{
                //    System.Diagnostics.Debugger.Break();
                //}
            }
            catch (System.OverflowException)
            {
                MessageBox.Show("たぶんGDI+の描画の限界");
            }
            catch (System.Runtime.InteropServices.ExternalException)
            {
                MessageBox.Show("たぶんGDI+の描画の限界");
            }
        }
    
        /// <summary>ChartがMouseMoveイベントを処理する前のイベント</summary>
        public event EventHandler<PreMouseEventArgs> PreMouseMove;
    
        protected override void OnMouseMove(MouseEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("ss.fff")+ "MOVE");
            if (tooltipVisible)
            {
                var p = System.Windows.Forms.Cursor.Position;
                var r=Math.Sqrt( Math.Pow(p.X - tooltioPos.X ,2)+ Math.Pow(p.Y-tooltioPos.Y,2));
                if(r>5)
                {
                    this.tooltip.Hide(this);
                    tooltipVisible = false;
                }
            }
            PreMouseEventArgs pm = new PreMouseEventArgs(e);
            if (PreMouseMove != null)
            {
                PreMouseMove(this, pm);
                if (pm.Cancel)
                {
                    return;
                }
            }
    
            base.OnMouseMove(e);
        }
        #endregion
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年2月26日 15:24

すべての返信

  • 繰り返すと数十秒応答がなくなって操作ができなくなるということですが、手元で試す限り、拡大をやり過ぎると OverflowException で止まってしまうことを確認しています。

    デバッグ実行されていて、Visual Studio でブレークされるまでの時間が長いと言うことを言われていると理解してよいのでしょうか?
    それとも、数十秒後にグラフはちゃんと表示されるのでしょうか?

    2017年2月25日 11:21
    モデレータ
  • ご返信ありがとうございます。

    ブレークされるまでの時間ではなく、スケール後に再描画されるまでの時間が数十秒かかります。また、その際にプロセスのCPU使用率が1コアの最大まで出ておりましたので、再描画で時間がかかっているのではないかと予想しております。

    OverflowExceptionについては、説明用にサンプルを作成しましたので問題点がございましたが、問題の本質ではありません。

    拡大→リセットを繰り返していると発生しやすいようです。


    2017年2月25日 11:58
  • なんだかChartがMouseMoveで行っている処理がかなり拡大した状態だとやたら時間がかかってるので、ChartのMouseMoveを無効にしてみる

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.DataVisualization.Charting;
    
    namespace ChartSample
    {
        public partial class Form1 : Form
        {
            private bool isMouseDown;
            private bool isMouseUp;
    
            private Point pointBefore;
            private Point pointAfter;
    
            public Form1()
            {
                InitializeComponent();
    
                ChartEx chart = new ChartEx();//継承したChartを使用
    
    
                chart.ChartAreas.Add(new ChartArea());
    
                chart.ChartAreas[0].AxisX.LabelStyle.Format = "0.00";
                chart.ChartAreas[0].AxisY.LabelStyle.Format = "0.00";
                chart.ChartAreas[0].AxisX2.LabelStyle.Format = "0.00";
                chart.ChartAreas[0].AxisY2.LabelStyle.Format = "0.00";
    
                chart.Series.Add(new Series());
                chart.Series.Add(new Series());
                chart.Series[0].ChartType = SeriesChartType.FastLine;
                chart.Series[1].ChartType = SeriesChartType.FastLine;
    
                chart.Series[1].XAxisType = AxisType.Secondary;
                chart.Series[1].YAxisType = AxisType.Secondary;
    
                chart.Dock = DockStyle.Fill;
    
                chart.Series[0].ToolTip = "x : #VALX\n#VALY";
    
                chart.MouseDown += chart_MouseDown;
                chart.MouseUp += chart_MouseUp;
                chart.PreMouseMove += chart_PreMouseMove;//元のMouseMoveイベントは使わない
                chart.PostPaint += chart_PostPaint;
    
                this.Controls.Add(chart);
    
                Random r = new Random();
                for (int x = 0; x < 50000; x += 1)
                {
                    chart.Series[0].Points.Add(new DataPoint(x, x & 1));
                }
                for (int x = 0; x < 50000; x += 1)
                {
                    chart.Series[1].Points.Add(new DataPoint(x, (x + 1) & 1));
                }
                var area = chart.ChartAreas[0];
    
                this.ClientSize = new Size(300, 300);
                area.AxisX.ScaleView.Zoom(40000.1473754173, 40000.8874906683);
                area.AxisY.ScaleView.Zoom(0.799297314829825, 0.799311445164638);
    
                area.AxisX2.ScaleView.Zoom(40000.1473754173, 40000.8874906683);
                area.AxisY2.ScaleView.Zoom(0.799297314829825, 0.799311445164638);
            }
    
            #region マウスによる拡大処理
            private void chart_MouseDown(object sender, MouseEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
    
                if (e.Button == MouseButtons.Right)
                {
                    this.isMouseDown = true;
                    this.isMouseUp = false;
                    this.pointBefore = e.Location;
                }
            }
    
            private void chart_MouseUp(object sender, MouseEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
                ChartArea area = chart.ChartAreas[0];
    
                if (e.Button == MouseButtons.Right)
                {
                    Axis axisX;
                    Axis axisY;
                    if (ModifierKeys == Keys.Shift)
                    {
                        axisX = area.AxisX2;
                        axisY = area.AxisY2;
                    }
                    else
                    {
                        axisX = area.AxisX;
                        axisY = area.AxisY;
                    }
    
                    this.pointAfter = e.Location;
    
                    if ((this.isMouseUp == true) &&
                        (this.pointBefore.X != this.pointAfter.X) &&
                        (this.pointBefore.Y != this.pointAfter.Y))
                    {
                        double bx = chartAreaPixelPositionToValue(axisX, chart.Width, this.pointBefore.X);
                        double by = chartAreaPixelPositionToValue(axisY, chart.Height, this.pointBefore.Y);
                        double ax = chartAreaPixelPositionToValue(axisX, chart.Width, this.pointAfter.X);
                        double ay = chartAreaPixelPositionToValue(axisY, chart.Height, this.pointAfter.Y);
                        axisX.ScaleView.Zoom(Math.Min(bx, ax), Math.Max(bx, ax));
                        axisY.ScaleView.Zoom(Math.Min(by, ay), Math.Max(by, ay));
                        this.isMouseDown = false;
                    }
                }
            }
    
            private void chart_PreMouseMove(object sender, PreMouseEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
                if (e.Button == MouseButtons.Right)
                {
                    this.pointAfter = e.Location;
                    chart.Invalidate();
                    this.isMouseUp = true;
                }
                e.Cancel = true;
            }
    
            private void chart_PostPaint(object sender, ChartPaintEventArgs e)
            {
                ChartEx chart = (ChartEx)sender;
    
                if ((e.ChartElement == chart) &&
                    (this.isMouseDown == true))
                {
                    float x = Math.Min(this.pointBefore.X, this.pointAfter.X);
                    float y = Math.Min(this.pointBefore.Y, this.pointAfter.Y);
                    float w = Math.Abs(this.pointBefore.X - this.pointAfter.X);
                    float h = Math.Abs(this.pointBefore.Y - this.pointAfter.Y);
    
                    Pen pen = new Pen(Color.Red, 3);
                    pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                    e.ChartGraphics.Graphics.DrawRectangle(pen, x, y, w, h);
                }
            }
    
            private double chartAreaPixelPositionToValue(Axis axis, int range, int point)
            {
                double ret = 0;
    
                if (0 > point)
                    point = 0;
    
                if (range < point)
                    point = range - 1;
    
                ret = axis.PixelPositionToValue(point);
    
                return ret;
            }
    
            #endregion
        }
    
    
        /// <summary>ズームの不具合を対策してみたChart</summary>
        class ChartEx : System.Windows.Forms.DataVisualization.Charting.Chart
        {
            private ChartScrollWorker scrollWorker;
    
            public ChartEx()
            {
                scrollWorker = new ChartScrollWorker(this);
            }
    
            #region Chartの本来のMouseMoveを無効化するための処理
    
            protected override void WndProc(ref Message m)
            {
                DateTime now = DateTime.Now;
                try
                {
                    base.WndProc(ref m);
    
                    var span = DateTime.Now - now;
                    //if (span.TotalSeconds > 5)
                    //{
                    //    System.Diagnostics.Debugger.Break();
                    //}
                }
                catch (System.OverflowException)
                {
                    MessageBox.Show("たぶんGDI+の描画の限界");
                }
                catch (System.Runtime.InteropServices.ExternalException)
                {
                    MessageBox.Show("たぶんGDI+の描画の限界");
                }
            }
    
            /// <summary>ChartがMouseMoveイベントを処理する前のイベント</summary>
            public event EventHandler<PreMouseEventArgs> PreMouseMove;
    
            protected override void OnMouseMove(MouseEventArgs e)
            {
                PreMouseEventArgs pm = new PreMouseEventArgs(e);
                if (PreMouseMove != null)
                {
                    PreMouseMove(this, pm);
                    if (pm.Cancel)
                    {
                        return;
                    }
                }
    
                base.OnMouseMove(e);
            }
            #endregion
        }
        /// <summary>ChartのScrollBarのマウスでのスクロール処理を置き換えるクラス</summary>
        class ChartScrollWorker
        {
            public ChartScrollWorker(ChartEx chart)
            {
                this.chart = chart;
                this.chart.AxisScrollBarClicked += Chart_AxisScrollBarClicked;
            }
            ChartEx chart;
            Axis targetAxis;
            double scrollBarPosition;
            double scrollBarLength;
            double viewLength;
            bool isHorizontal;
    
            void Chart_AxisScrollBarClicked(object sender, ScrollBarEventArgs e)
            {
    
                if (e.ButtonType == ScrollBarButtonType.ThumbTracker)
                {
                    //Chartのスクロールバーのツマミの場合
                    e.IsHandled = true;//元のMouseMoveを無効にする
    
                    this.targetAxis = e.Axis;
    
                    var area = targetAxis.ScrollBar.ChartArea;
                    var scrollBar = targetAxis.ScrollBar;
                    var size = scrollBar.Size;
                    var p1 = targetAxis.ValueToPixelPosition(targetAxis.ScaleView.ViewMinimum);
                    var p2 = targetAxis.ValueToPixelPosition(targetAxis.ScaleView.ViewMaximum);
                    Axis axisCross;
                    this.isHorizontal = area.AxisX == targetAxis || area.AxisX2 == targetAxis;
    
                    int buttonCount;
                    if (isHorizontal)
                    {
                        axisCross = area.AxisY;
                        buttonCount = 1;
                    }
                    else
                    {
                        axisCross = area.AxisX;
                        buttonCount = 0;
                    }
    
                    double legnth = Math.Abs(p1 - p2);
                    double pos = Math.Min(p1, p2);
                    double width = size;
                    if (axisCross.ScrollBar.IsVisible)
                    {
                        pos = pos + axisCross.ScrollBar.Size;
                        legnth = legnth - axisCross.ScrollBar.Size;
                    }
                    switch (scrollBar.ButtonStyle)
                    {
                        case ScrollBarButtonStyles.All:
                            pos = pos + scrollBar.Size * (buttonCount + 1);
                            legnth = legnth - scrollBar.Size * 2;
                            break;
                        case ScrollBarButtonStyles.ResetZoom:
                            pos = pos + scrollBar.Size * buttonCount;
                            legnth = legnth - scrollBar.Size * 1;
                            break;
                        case ScrollBarButtonStyles.SmallScroll:
                            pos = pos + scrollBar.Size * buttonCount;
                            legnth = legnth - scrollBar.Size * 2;
                            break;
                    }
    
                    //スクロールバーのスクロール可能範囲を登録
                    this.scrollBarPosition = pos;
                    this.scrollBarLength = legnth;
                    this.viewLength = targetAxis.ScaleView.ViewMaximum - targetAxis.ScaleView.ViewMinimum;
    
                    //マウスイベントをキャプチャ
                    this.chart.Capture = true;
                    this.chart.PreMouseMove += Chart_PreMouseMove;//元のMouseMoveより前に処理
                    this.chart.MouseUp += Chart_MouseUp;
                }
            }
    
            void Chart_PreMouseMove(object sender, PreMouseEventArgs e)
            {
                //スクロールバーのマウス位置に出来るだけあうようにスクロール
                var area = targetAxis.ScrollBar.ChartArea;
                double per;
                if (isHorizontal)
                {
                    per = Math.Min(Math.Max(0, (e.Location.X - scrollBarPosition) / scrollBarLength), 1);
                }
                else
                {
                    per = 1 - Math.Min(Math.Max(0, (e.Location.Y - scrollBarPosition) / scrollBarLength), 1);
                }
    
                double viewValue = (targetAxis.Maximum - targetAxis.Minimum) * per + targetAxis.Minimum;
                viewValue = Math.Min(viewValue, targetAxis.Maximum - viewLength);
                targetAxis.ScaleView.Zoom(viewValue, viewValue + viewLength);
    
                //e.Cancel = true;
            }
    
            void Chart_MouseUp(object sender, MouseEventArgs e)
            {
                chart.Capture = false;
                chart.PreMouseMove -= Chart_PreMouseMove;
                chart.MouseUp -= Chart_MouseUp;
            }
        }
    
        class PreMouseEventArgs : MouseEventArgs
        {
            public PreMouseEventArgs(MouseEventArgs e)
                : base(e.Button, e.Clicks, e.X, e.Y, e.Delta)
            {
            }
            public bool Cancel { get; set; }
        }
    }
    #細かくなりすぎてGDI+の描画できる限界なのかな?

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年2月26日 3:19
  • Chartが重くなる問題以外に、スクロールバーの不具合についても対応して頂きありがとうございます。

    後学のために以下の件についてご教授願います。

    ・ChartのOnMouseMoveをoverrideして通常時のMouseMoveで何もしないようにしているのは理解しているのですが、MouseMoveに問題があるというのは過去の経験からでしょうか。(パフォーマンス プロファイラーを使用しておりましたが、外部コードとなってしまい原因特定が困難でした)

    ・ScrollBarの不具合については既知の問題なのでしょうか。



    2017年2月26日 6:57
  • 肝心の問題解消できたのでしょうか?

    ・ChartのOnMouseMoveをoverrideして通常時のMouseMoveで何もしないようにしているのは理解しているのですが、MouseMoveに問題があるというのは過去の経験からでしょうか。(パフォーマンス プロファイラーを使用しておりましたが、外部コードとなってしまい原因特定が困難でした)

    ・ScrollBarの不具合については既知の問題なのでしょうか。

    ChartExのWndProcを見てもらえると判るでしょうが、ウィンドウメッセージの処理に時間がかかっているメッセージを調べて、それがWM_MOUSEMOVEでした。

    また、VSのオプションのデバッグで「マイコードのみを有効」を無効にしてやって、時間がかかっている状態になったらデバッガを中断にして、たとえばGraphicsPath.IsVisibleで止まってたら、メインスレッドのスタックフレームをみてやればMouseMoveから始まっていることが確認できます。

    ScrollBarの不具合というのが何をさしているのかわかりませんが、ChartのMouseMoveを無効にするとChartのスクロールバーが使えなくなるので、その代りとして無理やり処理させています。
    不具合が解消できたというのなら副次的な効果にすぎません。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年2月26日 9:54
  • 肝心の問題解消できたのでしょうか?

    いえ、問題の要因としてはgekka様のサンプルでわかりましたが、根本的な解決には至っていません。

    MouseMove時でキャンセル操作を行っているため、DataPointのツールチップが表示されなくなっております。そのため、処理負荷が軽減されて問題がしにくく(しない)状態になっておりますが、ツールチップを表示するようにすると問題が発生します。

            private void chart_PreMouseMove(object sender, PreMouseEventArgs e)
            {
                Chart chart = (Chart)sender;
    
                if (e.Button == MouseButtons.Right)
                {
                    this.pointAfter = e.Location;
                    chart.Invalidate();
                    this.isMouseUp = true;
                }
                else
                {
                    HitTestResult result = chart.HitTest(e.X, e.Y);
                    if (result.ChartElementType != ChartElementType.DataPoint)
                    {
                        e.Cancel = true;
                    }
                }
            }
    

    2017年2月26日 10:36
  • Tooltipが問題とわかっているなら自前でTooltip出せばいいだけのような…

    /// <summary>ズームの不具合を対策してみたChart</summary>
    class ChartEx : System.Windows.Forms.DataVisualization.Charting.Chart
    {
        private ChartScrollWorker scrollWorker;
        private ToolTip tooltip;
        private bool tooltipVisible;
        private Point tooltioPos;
        public ChartEx()
        {
            scrollWorker = new ChartScrollWorker(this);
            tooltip = new ToolTip();
            Application.Idle += Application_Idle;
        }
    
        void Application_Idle(object sender, EventArgs e)
        {
            var pos = System.Windows.Forms.Cursor.Position;
            pos = this.PointToClient(pos);
            if (tooltipVisible)
            {
                return;
            }
            if (0 <= pos.X && pos.X < this.Width && 0 <= pos.Y && pos.Y < this.Height)
            {
                double fx = (double)pos.X / this.Width * 100;
                double fy = (double)pos.Y / this.Height * 100;
                foreach (ChartArea area in this.ChartAreas)
                {
    
                    if (area.InnerPlotPosition.X <= fx && fx <= area.InnerPlotPosition.Right)
                    {
                        if (area.InnerPlotPosition.Y <= fy && fy <= area.InnerPlotPosition.Bottom)
                        {
                            var points = this.Series[0].Points;
                            var x = area.AxisX.PixelPositionToValue(pos.X);
                            double per = x / (area.AxisX.Maximum - area.AxisX.Minimum);
                            int index = (int)Math.Floor(points.Count * per);
                            index = Math.Min(Math.Max(0, index), points.Count - 1);
                            var p = points[index];
                            var size=System.Windows.Forms.Cursor.Current.Size;
                                
                            pos.Offset(size.Width/2,0);
    
                            tooltip.Show(p.ToString(), this,  pos) ;
                            tooltioPos = System.Windows.Forms.Cursor.Position;
                            tooltipVisible = true;
                            System.Diagnostics.Debug.WriteLine("SHOW");
                            break;
                        }
                    }
                }
            }
        }
    
        #region Chartの本来のMouseMoveを無効化するための処理
    
        protected override void WndProc(ref Message m)
        {
            DateTime now = DateTime.Now;
            try
            {
                if (m.Msg == 0x200)
                {
                    System.Diagnostics.Debug.WriteLine("WM_MOUSEMOVE");
                }
                base.WndProc(ref m);
    
                var span = DateTime.Now - now;
                //if (span.TotalSeconds > 5)
                //{
                //    System.Diagnostics.Debugger.Break();
                //}
            }
            catch (System.OverflowException)
            {
                MessageBox.Show("たぶんGDI+の描画の限界");
            }
            catch (System.Runtime.InteropServices.ExternalException)
            {
                MessageBox.Show("たぶんGDI+の描画の限界");
            }
        }
    
        /// <summary>ChartがMouseMoveイベントを処理する前のイベント</summary>
        public event EventHandler<PreMouseEventArgs> PreMouseMove;
    
        protected override void OnMouseMove(MouseEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("ss.fff")+ "MOVE");
            if (tooltipVisible)
            {
                var p = System.Windows.Forms.Cursor.Position;
                var r=Math.Sqrt( Math.Pow(p.X - tooltioPos.X ,2)+ Math.Pow(p.Y-tooltioPos.Y,2));
                if(r>5)
                {
                    this.tooltip.Hide(this);
                    tooltipVisible = false;
                }
            }
            PreMouseEventArgs pm = new PreMouseEventArgs(e);
            if (PreMouseMove != null)
            {
                PreMouseMove(this, pm);
                if (pm.Cancel)
                {
                    return;
                }
            }
    
            base.OnMouseMove(e);
        }
        #endregion
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年2月26日 15:24
  • ご指摘のとおり、各Seriesで持っているToolTipの表示に固執しており、自前で作成することを失念しておりました。

    サンプルまでご提供頂きありがとうございます、表題の問題について解決いたしました。

    2017年2月26日 16:23