none
WPF 绘制XY坐标 RRS feed

  • 问题

  • 各位好

           有个问题请教下,我想用WPF绘制二维坐标系,然后在坐标系上绘制点。我现在是用Canvas,通过new ellipse,然后将ellipse放在canvas相应的坐标点上。每隔75ms,clear掉canvas上的元素。再重新绘制,但现在发现内存不断增加,请教还有没有方法优化。

    2019年7月31日 7:24

全部回复

  • 你是如何添加的?

    每次都是一个吗?、

    最好是可以有代码

    有没有可能是VS的内存升高,误认为是程序了?

    我这面写了个一次添加一个,随即删除的例子

    未见内存会升高,基本稳定在43

       public MainWindow()
            {
                InitializeComponent();
                DispatcherTimer timer = new DispatcherTimer();
                timer.Interval = TimeSpan.FromMilliseconds(25);
                timer.Tick += Timer_Tick;
                timer.Start();
            }
    
            private Random r = new Random();
            private  void Timer_Tick(object sender, EventArgs e)
            {
                c.Children.Clear();
                SetEl(c, r.Next(0, (int)this.ActualWidth), r.Next(0, (int)this.ActualHeight));
            }
    
    
            private void SetEl(Canvas F, int X, int Y)
            {
                
                      Ellipse ellipse = new Ellipse
                      {
                          Height = 20,
                          Width = 20
                      };
                      Canvas.SetLeft(ellipse, X);
                      Canvas.SetTop(ellipse, Y);
                      ellipse.Fill = new SolidColorBrush(Colors.Red);
                      F.Children.Add(ellipse);
                  
            }

    2019年7月31日 7:54
  • 大哥你好,感谢你的回复。

    我现在把我的整个需求给你描述一下,希望能够得到你的一些指点,不胜感激。

    1.从下位机每隔75ms,读取一帧数据。每帧数据包含64个点。我需要实时的将这个64个点绘制到XY坐标系上。

    2.XY坐标系是我做的自定义控件,在这个控件内部定义了一个依赖属性Itemsource,每次itemsource变化都会调用一个callback,通过这个callback函数在坐标系上绘制出这64个点

    3.这64个数据不仅需要显示在坐标系上,还需要通过表格的形式显示出来。于是我定义了一个observablecollection 集合。然后将这个集合binding 到一个datagridview上。想通过这种方法实时显示。

    4.程序中,我定义了一个backgroundworker,在dowork事件里不停的采集下位机的数据。一旦采集完毕,就将这个数据reportprogress 到progresschanged 方法中显示(也就是上面描述的既要在坐标系上显示,又要做表格里显示)

    5.显示部分我用秒表测了下,大概绘制完毕需要20ms左右的时间。

    现在我的问题出现在:

    1.我有两个版本这个程序,在采集和显示这部分的代码几乎相同。我的显示只有20ms,按理说progresschanged方法中的代码应该每隔75ms执行一次。但其中一个程序只要我加入表格显示,就发现进程内存会不停的增大。而且显示那块是每隔10几ms就会执行,长时间占用UI线程,导致界面有点卡顿。另外一个版本的程序,表格显示那块没问题,但是只要加入坐标系绘制点,progresschanged 方法每隔100多ms才运行一次,导致无法实时刷新。

    //这个一个backworker,从下位机采集到的数据会存到一个队列里,然后这个负责从队列里不停的读取数据 
    
    private void Display_DoWork(object sender, DoWorkEventArgs e)
            {
                while (true)
                {                
                    if (!DispQueue.IsEmpty)//一旦队列中有数据就逐个显示
                    {                    
                        General temp = new General();                       
                        bool success = DispQueue.TryDequeue(out temp);                    
                        //temp=DataProcess.UpdateGeneralData(temp);
                        if (success )//只显示数据,开始标志不显示
                        {                           
                            if (temp.ID == 0x60A || temp.ID == 0x600)
                            {                           
                                DispFlagOccNum = DispFlagOccNum + 1;
                                if (DispFlagOccNum == 1)
                                {
                                    DispGeneralList.Clear();
                                }
                                else
                                {
                                    if (DispGeneralList.Count > 1)
                                    {
                                        List<General> datatemp = new List<General>();
                                        for (int i = 0; i < DispGeneralList.Count; i++)
                                        {
                                            datatemp.Add(DispGeneralList[i]);
                                        }
    ;                                   DispDataQueue.Enqueue(datatemp);
                                    }
                            }
                            else
                            {
                                DispGeneralList.Add(temp);
                             }
                                                }                    
                    }          
                }                   
            }

    //这是显示部分的代码 private void Display_ProgressChanged(object sender, ProgressChangedEventArgs e) { while(true) { if(Global.TotalDataFlag) { List<General> tempGeneralList = new List<General>(); bool success= DispDataQueue.TryDequeue(out tempGeneralList); if(success) { GeneralList.Clear(); ObservableCollection<PointDisp> tempPointList = new ObservableCollection<PointDisp>(); for (int i = 0; i < tempGeneralList.Count; i++) { PointDisp data = new PointDisp(); System.Windows.Point pointTemp = new System.Windows.Point(); pointTemp = DataProcess.PointDispProcess(tempGeneralList[i], 25); data.Position = pointTemp; data.ID = tempGeneralList[i].ID; tempPointList.Add(data); GeneralList.Add(tempGeneralList[i]); } pointDispList = tempPointList; Disp.ItemsSource = pointDispList; pointDispList.Clear(); } } } }

    还有如果您还是不太明白需求,18005203228我的手机号,有偿咨询,不胜感激,谢谢


    2019年7月31日 23:41
  • 我这面照你的描述

    1利用定时器 每75毫秒执行获取XY坐标点方法,其中使用线程等待10毫秒,模拟链接

    2验证数据是否64个,以及创建两个集合,分别是坐标点和DataGrid数据表

    3使用自定义事件模拟progresschanged 进行绘制坐标和数据表

    编写代码中 第一次确实有随时间内存逐渐增高的情况。

    是DataGrid的数据源问题。

    修改后 程序运行10分钟左右

    未见内存增高 在50~64之间徘徊

    代码如下:

    namespace Canvas_El
    {
        public class PointData
        {
            public double PointX { get; set; }
            public double PointY { get; set; }
        }
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                DispatcherTimer timer = new DispatcherTimer();
                timer.Interval = TimeSpan.FromMilliseconds(75);
                timer.Tick += Timer_Tick;
                timer.Start();
                OnGetData += MainWindow_OnGetData;
               
            }
    
            private ObservableCollection<PointData> pointDatas = new ObservableCollection<PointData>();
    
            private ObservableCollection<PointData> points;
    
            private Random r = new Random();
    
            private event Action<int> OnGetData;
    
            private void MainWindow_OnGetData(int obj)
            {
                CD.Text = points.Count.ToString();
                SD.Text = pointDatas.Count.ToString();
                if (obj >= 63)
                {
                    c.Children.Clear();
                 
                    for (var item =0;item<points.Count;item++)
                    {
                        SetEl(c, points[item].PointX, points[item].PointY);
                       
                    }
    
                }
            }
    
            private  void Timer_Tick(object sender, EventArgs e)
            {
                points = BackGroundWorker();
    
                foreach(var item in points)
                pointDatas .Add(item);
    
                ShowData.ItemsSource = pointDatas;
                var pu = pointDatas.Count;
                for (var i = 0; i < pu / 2; i++)
                    pointDatas.RemoveAt(i);
                OnGetData?.Invoke(points.Count);
    
                //  c.Children.Clear();
                // SetEl(c, r.Next(0, (int)this.ActualWidth), r.Next(0, (int)this.ActualHeight));
            }
    
            private ObservableCollection<PointData> BackGroundWorker()
            {
                Thread.Sleep(10);
    
                ObservableCollection<PointData> list = new ObservableCollection<PointData>();
                for (var i = 0; i < 64; i++)
                {
                    PointData point = new PointData();
    
                    point.PointX = r.Next(0, (int)c.ActualWidth);
                    point.PointY = r.Next(0, (int)c.ActualHeight);
    
                    list.Add(point);
    
                }
    
                return list;
            }
    
            private void SetEl(Canvas F, double X, double Y)
            {
    
                Ellipse ellipse = new Ellipse
                {
                    Height = 20,
                    Width = 20
                };
                Canvas.SetLeft(ellipse, X);
                Canvas.SetTop(ellipse, Y);
                ellipse.Fill = new SolidColorBrush(Colors.Red);
                F.Children.Add(ellipse);
    
            }
        }
    

    2019年8月1日 2:18
  • 还想追问下大哥,datagrid 数据源应该怎么处理。我看了您上述的代码,恕小弟新手,get不到应该需要注意什么。
    2019年8月1日 4:58
  • 我现在把datagrid那块拿出来 ,专门写个了程序,发现内存还是不断上升,而且界面还有点卡顿
    2019年8月1日 5:04
  • 我不清楚你的datagrid数据源怎么处理

    不过要保证

    1 数据独立性

    2 数据完整性

    独立性就是 保证数据是独立,非引用的,就是独立存在的

    完整性数据是准确的 唯一的,或者说符合计算的

    我的代码 第一次出现内存上涨是因为 dg的数据源是引用绘制点的数据

    改成独立之后 就不会有这样的问题

    如果你能将完整的代码贴出 这样子能更好的解决问题

    2019年8月1日 5:04
  • using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using canlibCLSNET;
    using Kvaser.Kvadblib;
    
    namespace PointCollection
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            BackgroundWorker CanReceive = new BackgroundWorker();
            BackgroundWorker Display = new BackgroundWorker();
            Kvadblib.Hnd dbhandle;//dbc文件操作句柄
            ConcurrentQueue<General> DispQueue = new ConcurrentQueue<General>();//用于显示采集到的object数据 
    
            int DispFlagOccNum = 0;
            ObservableCollection<General> DispGeneralList = new ObservableCollection<General>();
            
            ObservableCollection<PointDisp> pointDispList = new ObservableCollection<PointDisp>();
            ObservableCollection<General> GeneralList = new ObservableCollection<General>();//用于存储采集到的数据,binding 到datagrid显示控件
            List<string> currenttimeB = new List<string>();
            List<string> currenttimeA = new List<string>();
    
            List<float> timeWork = new List<float>();
            public MainWindow()
            {
                Canlib.canInitializeLibrary();
                InitializeComponent();
                CanReceive.DoWork += CanReceive_DoWork;
                CanReceive.WorkerReportsProgress = true;
                CanReceive.ProgressChanged += CanReceive_ProgressChanged;
                CanReceive.RunWorkerAsync();
                Display.DoWork += Display_DoWork;
                Display.WorkerReportsProgress = true;
                Display.ProgressChanged += Display_ProgressChanged;
                Display.RunWorkerAsync();
               
                string filename = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, @"Data\radar CAN20190627.dbc"); ;//dbc文件的路径
                dbhandle = new Kvadblib.Hnd();
                Kvadblib.Status dbstatus;
                dbstatus = Kvadblib.Open(out dbhandle);
                dbstatus = Kvadblib.ReadFile(dbhandle, filename);
    
                dgDetailData.ItemsSource = GeneralList;
            }
    
            private void Display_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                try
                {
                    //b = b + 1;
                    //List<General> GeneralListTemp = new List<General>();
                    List<General> GeneralListTemp = e.UserState as List<General>;                            
                    if (true)
                    {
                        string ct = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss fff");
                        currenttimeA.Add(ct);
                        if (GeneralListTemp.Count != 0)
                        {
                            GeneralList.Clear();
                            
                            Stopwatch watch = Stopwatch.StartNew();//创建一个监听器  
                            for (int i = 0; i < GeneralListTemp.Count; i++)
                            {
                               GeneralList.Add(GeneralListTemp[i]);
    
                            }
                            watch.Stop();
                            float sec = watch.ElapsedMilliseconds;
                            timeWork.Add(sec);
                            //Disp.CreatePointandLine(pointDispList);                       
                            pointDispList.Clear();
                        }
                        else
                            MessageBox.Show("Error");
                    }               
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());                
                }
            }
    
            private void Display_DoWork(object sender, DoWorkEventArgs e)
            {
                while (true)
                {
                    if (!DispQueue.IsEmpty)//一旦队列中有数据就逐个显示
                    {
                        General temp = new General();
                        bool success = DispQueue.TryDequeue(out temp);                   
                        if (success)//只显示数据,开始标志不显示
                        {
                            if (temp.ID == 0x60A || temp.ID == 0x600)
                            {
                                DispFlagOccNum = DispFlagOccNum + 1;
                                if (DispFlagOccNum == 1)
                                {
                                    DispGeneralList.Clear();
                                }
                                else
                                {
                                    if (DispGeneralList.Count > 1)
                                    {
                                        List<General> datatemp = new List<General>();                                    
                                        
                                        for(var i=0;i<DispGeneralList.Count;i++)
                                        {
                                            datatemp.Add(DispGeneralList[i]);
                                        }
                                        //Stopwatch watch = Stopwatch.StartNew();//创建一个监听器                                     
                                        Display.ReportProgress(0, datatemp);
                                        string ct = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss fff");
                                        currenttimeB.Add(ct);
                                        
                                        DispGeneralList.Clear();
                                        //watch.Stop();
                                        //float sec = watch.ElapsedMilliseconds;
                                        //timeWork.Add(sec);
                                    }
                                }
                            }
                            else
                            {
                                DispGeneralList.Add(temp);                           
                            }                       
                        }
                    }
                }
            }
    
            private void CanReceive_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                throw new NotImplementedException();
            }
    
            private void CanReceive_DoWork(object sender, DoWorkEventArgs e)
            {
                int channel = 0;
                int readHandle = Canlib.canOpenChannel(channel, Canlib.canOPEN_ACCEPT_VIRTUAL);
                Canlib.canStatus status = Canlib.canSetBusParams(readHandle, Canlib.BAUD_500K, 0, 0, 0, 0, 0);
                
                
                bool noError = true;
                status = Canlib.canBusOn(readHandle);
                int id;
    
                int dlc;
                int flags;
                long time;
                string msg;
                
              
                while ((status == Canlib.canStatus.canOK) && noError && readHandle >= 0)
                {
                    try
                    {
                        byte[] receiveData = new byte[8];
                        status = Canlib.canReadWait(readHandle, out id, receiveData, out dlc, out flags, out time, 10000);
                        if (status == Canlib.canStatus.canOK)
                        {                       
                            if (id == 0x60B || id == 0x60A || id == 0x701 || id == 0x600)
                            {
                                General General = new General();
                                if (id == 0x60A || id == 0x600)
                                {
                                    General.ID = id;                                
                                    DispQueue.Enqueue(General);
                                    //string ct = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss fff");
                                    //currenttimeA.Add(ct);
                                }
                                else
                                {
                                    General = DumpGeneralMessage(id, receiveData, dlc, flags, time);
                                    try
                                    {
                                        //Stopwatch watch = Stopwatch.StartNew();//创建一个监听器
                                        //CSVQueue.Enqueue(General);
                                        //watch.Stop();
                                        //float sec = watch.ElapsedMilliseconds;
                                        DispQueue.Enqueue(General);//用于数据显示
                                       
                                    }
                                    catch (Exception ex)
                                    {
                                        MessageBox.Show(ex.ToString());
                                    }
                                }
                            }
                        }
                    }
    
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.ToString());
                        System.Environment.Exit(0);
                    }
                }
                Canlib.canBusOff(readHandle);
                MessageBox.Show("TimeOut");
            }
    
    
            private General DumpGeneralMessage(int id, byte[] data, int dlc, int flags, long time)
            {
                Kvadblib.Status status;
                Kvadblib.MessageHnd mh = new Kvadblib.MessageHnd();
                Kvadblib.SignalHnd sh = new Kvadblib.SignalHnd();
    
    
                //Flips the EXT bit if the EXT flag is set
                if ((flags & Canlib.canMSG_EXT) != 0)
                {
                    id ^= -2147483648;
                }
    
                //Find the database message whose id matches the one
                //from the incoming message
                status = Kvadblib.GetMsgById(dbhandle, id, out mh);
                //Console.WriteLine("Reading message with id " + id);
                //DisplayDBError(status, "Reading message with id " + id);
    
                //Print the message info
                General Data = new General();
                if (status == Kvadblib.Status.OK)
                {
                    string msgName;
                    status = Kvadblib.GetMsgName(mh, out msgName);
    
                    //Console.WriteLine("Message received: {0}", msgName);
                    //StatusUpdate("", "Message received: " + msgName);
                    int msgId;
                    Kvadblib.MESSAGE msgFlags;
                    status = Kvadblib.GetMsgId(mh, out msgId, out msgFlags);
    
                    //Console.WriteLine("Id: {0}, flags: {1}", msgId, msgFlags);
                    //StatusUpdate("", "Id: " + msgId + "flags: " + msgFlags);
    
                    //Iterate through all the signals and print their name, value and unit
                    status = Kvadblib.GetFirstSignal(mh, out sh);
                    while (status == Kvadblib.Status.OK)
                    {
                        Data.TimeStamp=  DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss fff");
                        //Data.TimeStamp = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + " " + DataProcess.ConvertLongToDateTime(time).ToString("fff");
                        string signalname;
                        status = Kvadblib.GetSignalName(sh, out signalname);
    
                        string unit;
                        status = Kvadblib.GetSignalUnit(sh, out unit);
    
                        double value;
                        status = Kvadblib.GetSignalValueFloat(sh, out value, data, dlc);
                        switch (signalname)
                        {
                            case "Object_ID_60B":
                                {
                                    Data.ID = Convert.ToInt16(value);
                                    break;
                                }
                            case "Cluster_ID_701":
                                {
                                    Data.ID = Convert.ToInt32(value);
                                    break;
                                }
                            case "Object_DistLog":
                                {
                                    Data.DistLong = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Cluster_DistLong":
                                {
                                    Data.DistLong = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Object_DistLat":
                                {
                                    Data.DistLat = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Cluster_DistLat":
                                {
                                    Data.DistLat = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Object_VrelLog":
                                {
                                    Data.VrelLong = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Cluster_VrelLong":
                                {
                                    Data.VrelLong = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Object_DynProp":
                                {
                                    Data.DynProp = (DynProp)value;
                                    break;
                                }
                            case "Cluster_DynProp":
                                {
                                    Data.DynProp = (DynProp)value;
                                    break;
                                }
                            case "Object_VrelLat":
                                {
                                    Data.VrelLat = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Cluster_VrelLat":
                                {
                                    Data.VrelLat = Convert.ToSingle(value);
                                    DataProcess.UpdateGeneralData(Data);
                                    break;
                                }
                            case "Object_RCS":
                                {
                                    Data.RCS = Convert.ToSingle(value);
                                    break;
                                }
                            case "Cluster_RCS":
                                {
                                    Data.RCS = Convert.ToSingle(value);
                                    break;
                                }
                        }
    
                        //Console.WriteLine("Signal - {0}: {1} {2}", signalname, value, unit);
                        //StatusUpdate("", "Signal: " + signalname + " " + value+" "+unit);
                        status = Kvadblib.GetNextSignal(mh, out sh);
                    }
                    return Data;
                }
                else
                {
                    MessageBox.Show(status.ToString());
                    return null;
                }
    
            }
    
    
            public class PointDisp : INotifyPropertyChanged
            {
                public event PropertyChangedEventHandler PropertyChanged;
                private int id;
                public int ID
                {
                    get
                    {
                        return id;
                    }
                    set
                    {
                        id = value;
                        if (this.PropertyChanged != null)
                        {
                            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("ID"));
                        }
    
                    }
                }
    
                private Point position;
                public Point Position
                {
                    get
                    {
                        return position;
                    }
                    set
                    {
                        position = value;
                        if (this.PropertyChanged != null)
                        {
                            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Position"));
                        }
                    }
                }
            }
        }
    }
    

    代码中几个函数的作用:

    1.CanReceive_DoWork:一直从下位机接受Can报文,将接受到的报文传入一个队列

    2.Display_DoWork:从队列里取数据,然后处理,满足64个就通过reportprogress 传入到progresschanged中显示

    3.Display_ProgressChanged:就是不停的将数据add到绑定的集合里,绑定之前要先将集合clear掉显示最新的数据(我刚刚试验,如果不clear,不停的添加,就不会出现内存增加和界面卡顿的现象,但是我解释不了其中的原因)

    4.主要就是这三个函数,其他函数没什么关系。

    谢谢

    2019年8月1日 5:41
  • 你要不要试试不要直接clear

    和我一样使用remove这样的方式?

    等待数据全部ADD

    获取集合当前count

    利用循环删除0-当前count/2所有数据,只保留当前加载的?

    for (int i = 0; i < GeneralListTemp.Count; i++) { GeneralList.Add(GeneralListTemp[i]); } var ct = GeneralList.Count;

    if(ct>64) for (var i = 0; i < ct / 2; i++) GeneralList.RemoveAt(i);




    • 已编辑 ARM830 2019年8月1日 6:17
    2019年8月1日 6:15
  • 感谢。

    但是只是目前这个下位机会固定输出64个目标,其他输出的不是固定的。

    还有为什么 clear()会影响内存呢?

    如果我现在不把数据显示在表格上,就可以确保在坐标系上画点是每隔75ms执行一次,如果一旦显示表格的数据,就会发现是每隔100多ms才会执行一次progresschanged 函数。一直搞不懂是什么原因。

    难道是UI线程就一个,这两种显示需要共享UI线程?不太确定

    还望高手不吝赐教。谢谢

    2019年8月1日 7:48
  • 1

     如果remove这个方法管用,就算是数量不一样也只是数学问题。

    2

    clear这个操作内部操作涉及很多,具体也可参考

    DataGrid

    Observalecollection

    3

    UI线程有两个,一个渲染,一个管理,我觉你想表达是 管理这个线程,这个是只有一个。

    具体参考WPF线程模型

    4

    如果觉得progresschanged方法有卡,可以尝试使用UI线程的异步方法。

    或者将DataGrid改成其他控件ListBox,ListView都可以,轻微改动Style/ItemTemplate就可以成为一个轻型DataGrid表格,

    Litsbox和LitsView内部代码,原理与DataGrid更简单,方便


    2019年8月1日 9:15
  • 感谢这么细致的解释
    2019年8月2日 1:12