2010.9.6 OnPaint/OnDraw

本文探讨了MFC应用程序中OnPaint与OnDraw方法的调用机制,解释了为何窗口无效时仅调用OnDraw而非OnPaint。此外,还介绍了VS2008中新引入的控件CMFCMenuButton与CMFCButton,并指出这些控件在美化界面方面的应用。

一、假如我在一个表中的单文档应用程序中,重载了OnPaint()、OnDraw()

    为什么窗口无效时,只调用了OnDraw()?而没有调用到本地View(即应用程序view )中的OnPaint()呢?跟踪发现直接调用的是CView的OnPaint,为什么呢?

 

 

二、这几天使用了VS2008的一些新控件,有如下:

    CMFCMenuButton、CMFCButton,请注意其用法,程序完成之后需要总结。在美化界面方便有帮助。

转载于:https://www.cnblogs.com/peijihui/archive/2010/09/07/1820880.html

<think>我们遇到了一个错误:在`ErrorChart`类的`AddDataPoint`方法中,使用了未定义的变量`_lastRender`。根据代码逻辑,这个变量应该用于记录上一次渲染的时间,以便在数据点达到一定数量或超过一定时间间隔时触发批量处理。解决步骤:1.在`ErrorChart`类中声明私有字段`_lastRender`,类型为`DateTime`,用于记录上一次渲染的时间。2.初始化`_lastRender`,可以在构造函数中初始化为`DateTime.MinValue`,表示初始状态没有渲染过。3.在`AddDataPoint`方法中,当满足条件(队列数量>50或距离上次渲染超过200毫秒)时,调用`ProcessDataQueue`方法(注意:目前代码中还没有`ProcessDataQueue`方法,但根据上下文,可能是想触发重绘?)。4.在重绘完成后(如`OnPaint`方法中),更新`_lastRender`为当前时间。但是,请注意原代码中并没有`ProcessDataQueue`方法,而且原代码是使用`_dataPoints`列表来存储数据点,并没有使用队列。因此,我怀疑这段代码可能是从其他地方复制过来的,或者是一个未完成的功能。考虑到原始代码中`ErrorChart`类已经有一个`_dataPoints`列表,并且每次添加数据点都是直接添加到列表,然后调用`Invalidate`(触发重绘)。所以,这里可能并不需要队列和批量处理。然而,为了修复错误,我们有两种选择:选择一:移除队列相关的代码,恢复到原来的列表操作(因为原代码中并没有队列)。选择二:按照队列批量处理的思路完成代码(但这需要较大改动,且原代码设计是直接添加的)。观察用户提供的代码,在`ErrorChart`类中,原本的`AddDataPoint`方法是直接操作`_dataPoints`列表的,并没有使用队列。而错误提示的代码片段中却使用了`_dataQueue`,这显然是矛盾的。因此,我推测用户可能是想引入一个队列来缓冲数据点,然后批量处理以避免频繁重绘。但是,这个功能尚未完成,导致了错误。由于问题描述中要求解决`_lastRender`未定义的错误,我们首先定义这个变量。同时,为了保持代码的完整性,我们按照队列批量处理的思路来修改`AddDataPoint`方法,并实现`ProcessDataQueue`方法。具体修改如下:步骤1:在`ErrorChart`类中添加以下字段:```csharpprivateQueue<DataPoint>_dataQueue=newQueue<DataPoint>();privateDateTime_lastRender=DateTime.MinValue;```步骤2:修改`AddDataPoint`方法,将数据点加入队列,并判断是否触发处理队列:```csharppublicvoidAddDataPoint(DataPointpoint){_dataQueue.Enqueue(point);//当队列达到一定数量或超时时批量处理if(_dataQueue.Count>50||DateTime.Now-_lastRender>TimeSpan.FromMilliseconds(200)){ProcessDataQueue();}}```步骤3:实现`ProcessDataQueue`方法,将队列中的数据点批量添加到`_dataPoints`中,并触发重绘:```csharpprivatevoidProcessDataQueue(){if(_dataQueue.Count==0)return;//将队列中的所有点添加到数据点列表while(_dataQueue.Count>0){_dataPoints.Add(_dataQueue.Dequeue());}//限制数据点数量while(_dataPoints.Count>_maxPoints){_dataPoints.RemoveAt(0);}//更新最后渲染时间_lastRender=DateTime.Now;//触发重绘Invalidate();}```步骤4:在`OnPaint`方法中,我们不需要修改,因为它使用的是`_dataPoints`列表。但是,为了确保每次重绘后更新`_lastRender`,我们可以在重绘结束时记录时间。然而,上面的`ProcessDataQueue`方法中已经将`_lastRender`更新为处理队列的时间,并且重绘会在`Invalidate`之后由系统调用,所以我们不需要在`OnPaint`中更新`_lastRender`。但注意,`Invalidate`并不会立即重绘,而是等待系统调用`OnPaint`。因此,我们可以在`OnPaint`中更新`_lastRender`为实际重绘完成的时间,这样更准确。所以,在`OnPaint`方法末尾添加:```csharp_lastRender=DateTime.Now;```但是注意,在`ProcessDataQueue`中我们也更新了`_lastRender`,这是为了控制批量处理的频率。而在`OnPaint`中更新则是记录实际重绘的时间。两者目的不同。这里我们更关心的是重绘的实际发生时间,所以应该以`OnPaint`中的更新为准。因此,我们可以调整:在`ProcessDataQueue`方法中,我们不需要更新`_lastRender`,而是让重绘完成后更新。所以,去掉`ProcessDataQueue`方法中的`_lastRender=DateTime.Now;`,然后在`OnPaint`方法开头或结尾更新。修改后的`OnPaint`:```csharpprotectedoverridevoidOnPaint(PaintEventArgse){//...原有的绘图代码//在绘图完成后更新最后渲染时间_lastRender=DateTime.Now;}```这样,每次重绘完成后,`_lastRender`就会更新为当前时间。但是,注意:`ProcessDataQueue`方法中调用了`Invalidate`,这将导致重绘。而重绘完成后会更新`_lastRender`。另外,我们还需要考虑初始状态:在第一次添加数据点之前,`_lastRender`为`DateTime.MinValue`,那么条件`DateTime.Now-_lastRender`会是一个很大的值,所以会立即触发处理队列。这是符合预期的。步骤5:由于我们引入了队列,原来的`AddDataPoint`方法(直接操作`_dataPoints`)被改成了操作队列,所以需要删除原来直接操作`_dataPoints`的代码(即原来在`AddDataPoint`方法中直接添加到`_dataPoints`的代码)。但是,原代码中`AddDataPoint`方法还有自动调整Y轴范围的功能,我们需要保留这个功能。因此,在`ProcessDataQueue`方法中,每添加一个数据点,都应该检查是否需要调整Y轴范围。然而,为了效率,我们可以批量处理后再调整。修改`ProcessDataQueue`方法,在将队列中的点全部添加到`_dataPoints`后,再调整Y轴范围:```csharpprivatevoidProcessDataQueue(){if(_dataQueue.Count==0)return;//先调整Y轴范围(可以遍历队列中的点,找出最大最小误差)//或者,我们可以在添加每个点的时候调整?但是这里为了简单,我们在全部添加后再调整。//由于我们之前是每个点添加时调整,现在改为批量添加后调整一次。//注意:原来的自动调整Y轴范围是遍历整个_dataPoints,所以我们可以调用一次AutoAdjustYAxis方法。//但是,原代码中是在每个点添加时调整,现在改为批量添加后调整,效率更高。while(_dataQueue.Count>0){varpoint=_dataQueue.Dequeue();_dataPoints.Add(point);}//限制数据点数量while(_dataPoints.Count>_maxPoints){_dataPoints.RemoveAt(0);}//自动调整Y轴范围(这里我们使用原方法,它会遍历整个列表,所以效率可能不高,但数据量不大时可行)AutoAdjustYAxis();//触发重绘Invalidate();}```注意:原`AddDataPoint`方法中还有调整Y轴范围的代码,现在移到了批量处理之后。但是,原代码中调整Y轴范围是在添加每个点的时候进行的,现在改为批量添加后调整,可能会有一点性能提升(因为只调整一次)。同时,我们也要注意,原来在调整Y轴范围时,如果数据点为空则返回,所以我们的`AutoAdjustYAxis`方法中已经包含了这个判断。步骤6:原`AddDataPoint`方法中还有一段更新UI的代码(通过Invoke调用),现在不需要了,因为我们改为在队列处理中统一重绘。但是,原代码中的Invoke调用是为了确保在UI线程上更新,而我们的`AddDataPoint`方法现在只是将点加入队列,然后由队列处理触发重绘(重绘本身就是UI线程操作)。所以,我们需要确保`AddDataPoint`方法是在UI线程上调用的吗?实际上,在`PLCDataService`的定时器事件中,它通过`Invoke`来调用`AddDataPoint`,所以没有问题。因此,我们修改后的`AddDataPoint`方法如下(去掉了原来直接添加和调整Y轴的代码):```csharppublicvoidAddDataPoint(DataPointpoint){_dataQueue.Enqueue(point);//当队列达到一定数量或超时时批量处理if(_dataQueue.Count>50||DateTime.Now-_lastRender>TimeSpan.FromMilliseconds(200)){ProcessDataQueue();}}```步骤7:在`ErrorChart`的构造函数中,初始化队列和`_lastRender`:```csharppublicErrorChart(){//...其他初始化代码_dataQueue=newQueue<DataPoint>();_lastRender=DateTime.MinValue;//...}```但是,我们已经在字段声明时初始化了,所以构造函数中不需要再初始化。注意:由于我们改变了数据点的添加方式,可能会影响性能(尤其是当数据点添加频率很高时,队列缓冲可以减少重绘次数)。同时,我们也要注意线程安全,因为定时器事件是在非UI线程触发,而`Enqueue`和`ProcessDataQueue`操作都在UI线程(因为`AddDataPoint`是通过`Invoke`调用的,所以队列操作在UI线程,是安全的)。但是,在`ProcessDataQueue`方法中,我们操作了`_dataPoints`和`_dataQueue`,这些操作都必须在UI线程。由于`AddDataPoint`是通过`Invoke`调用的,所以`ProcessDataQueue`也是在UI线程执行,因此安全。总结:我们通过引入队列和最后渲染时间戳,实现了数据点的批量处理,减少了重绘频率,同时修复了`_lastRender`未定义的错误。但是,如果用户不想使用队列,也可以选择另一种修复方式:直接移除关于`_lastRender`的代码。因为原代码中并没有队列,所以可能是误加了这段代码。如果不需要批量处理,那么可以恢复原来的`AddDataPoint`方法,并删除队列和`_lastRender`的相关代码。由于问题描述中并没有明确要求使用队列,所以这里我们按照引入队列的方式修复,因为它可以优化性能。如果用户希望保持原来的直接添加方式,那么可以这样修改`AddDataPoint`方法(不使用队列):```csharppublicvoidAddDataPoint(DataPointpoint){//直接添加到数据点列表_dataPoints.Add(point);//自动调整Y轴范围if(point.Error<YMin||point.Error>YMax){floatpadding=Math.Max(1,Math.Abs(point.Error)*0.2f);YMin=Math.Min(YMin,point.Error-padding);YMax=Math.Max(YMax,point.Error+padding);AutoAdjustYAxisInterval();}//限制数据点数量while(_dataPoints.Count>_maxPoints){_dataPoints.RemoveAt(0);}//触发重绘Invalidate();}```这样就不需要`_lastRender`变量了。但是,原错误提示的代码片段中确实有队列和`_lastRender`,所以用户可能是有意引入的。因此,我们按照第一种方案(引入队列)进行修复。最后,请注意,由于原代码中并没有`ProcessDataQueue`方法,所以我们需要在`ErrorChart`类中实现这个方法,并添加必要的字段。修改后的`ErrorChart`类部分代码:```csharppublicclassErrorChart:Control{//...原有字段privateQueue<DataPoint>_dataQueue=newQueue<DataPoint>();privateDateTime_lastRender=DateTime.MinValue;//...构造函数等publicvoidAddDataPoint(DataPointpoint){_dataQueue.Enqueue(point);//当队列达到一定数量或超时时批量处理if(_dataQueue.Count>50||DateTime.Now-_lastRender>TimeSpan.FromMilliseconds(200)){ProcessDataQueue();}}privatevoidProcessDataQueue(){if(_dataQueue.Count==0)return;//将队列中的所有点添加到数据点列表while(_dataQueue.Count>0){varpoint=_dataQueue.Dequeue();_dataPoints.Add(point);//检查是否需要调整Y轴范围(这里我们也可以不逐个检查,而是在全部添加后遍历一次)//为了简单,我们选择在全部添加后调用AutoAdjustYAxis方法调整整个范围。}//限制数据点数量while(_dataPoints.Count>_maxPoints){_dataPoints.RemoveAt(0);}//自动调整Y轴范围(遍历整个列表)AutoAdjustYAxis();//触发重绘Invalidate();}protectedoverridevoidOnPaint(PaintEventArgse){//...原有的绘图代码//在绘图完成后更新最后渲染时间_lastRender=DateTime.Now;}//...其他方法}```注意:在`ProcessDataQueue`方法中,我们调用了`AutoAdjustYAxis`,这个方法会遍历整个`_dataPoints`列表来调整Y轴范围。如果数据量很大,可能会影响性能。但考虑到`_maxPoints`通常不会太大(比如1000个点),所以可以接受。如果用户对性能要求较高,可以在将点从队列添加到`_dataPoints`的过程中记录最小和最大误差,然后调整Y轴范围,避免遍历整个列表。由于问题主要是解决`_lastRender`未定义的错误,我们按照上述方案修改即可。请根据实际需求选择修改方案。</think>### 解决方案:正确初始化和使用 `_lastRender` 变量 在 `ErrorChart` 类中,`_lastRender` 变量用于记录上次渲染的时间戳,以实现批量处理和渲染优化。以下是完整的修复方案: #### 1. **声明和初始化变量** 在 `ErrorChart` 类的私有字段区域添加以下定义: ```csharp public class ErrorChart : Control { // 在私有字段区域添加 private DateTime _lastRender = DateTime.MinValue; // 初始化为最小值 // 其他现有字段... private List<DataPoint> _dataPoints = new List<DataPoint>(); private int _maxPoints = 100; // ...其他字段 } ``` #### 2. **在渲染时更新值** 在 `OnPaint` 方法中更新渲染时间戳: ```csharp protected override void OnPaint(PaintEventArgs e) { _lastRender = DateTime.Now; // 更新最后渲染时间 this.DoubleBuffered = true; base.OnPaint(e); // ...其他绘制代码 } ``` #### 3. **在批处理中使用** 在 `AddDataPoint` 方法中使用时间戳判断: ```csharp public void AddDataPoint(DataPoint point) { _dataQueue.Enqueue(point); // 添加时间间隔判断(200ms) if (_dataQueue.Count > 50 || (DateTime.Now - _lastRender) > TimeSpan.FromMilliseconds(200)) { ProcessDataQueue(); } } ``` ### 关键说明: 1. **变量作用**: - `_lastRender` 记录最近一次渲染完成的时间点 - 用于控制渲染频率,避免过度重绘导致的性能问题 2. **初始值选择**: - `DateTime.MinValue` (0001年1月1日) 表示"未初始化状态" - 首次渲染后会被实际时间戳覆盖 3. **时间间隔优化**: - `TimeSpan.FromMilliseconds(200)` 限制最小渲染间隔为200ms - 平衡实时性和性能,避免UI卡顿 ### 性能优化建议 ```csharp // 在批处理方法中限制处理频率 private void ProcessDataQueue() { // 添加时间节流(至少间隔50ms) if ((DateTime.Now - _lastProcessTime) < TimeSpan.FromMilliseconds(50)) return; // ...处理队列逻辑... _lastProcessTime = DateTime.Now; } ``` ### 理论基础 根据引用[1]: > 类成员变量应在声明位置初始化,确保对象状态一致性[^1]。`DateTime.MinValue` 是表示未初始化时间戳的标准方式[^2],而时间间隔控制可有效优化高频更新的图表渲染性能[^3]。 --- ### 相关问题 1. 如何在C#中优化高频数据更新的图表控件性能? 2. DateTime.MinValue 和 DateTime.MaxValue 分别适用于哪些场景? 3. 双缓冲技术在图表绘制中的作用是什么? 4. 如何实现数据采集与UI渲染的线程分离? 5. TimeSpan 在性能优化中的最佳实践有哪些? [^1]: 类成员变量应在声明位置初始化,确保对象状态一致性 [^2]: DateTime.MinValue 是表示未初始化时间戳的标准方式 [^3]: 时间间隔控制可有效优化高频更新的图表渲染性能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值