【问题标题】:In C#, is there a way to call ProgressBar's paint logic to make it paint into a DataGridViewImageCell?在 C# 中,有没有办法调用 ProgressBar 的绘制逻辑以使其绘制到 DataGridViewImageCell 中?
【发布时间】:2011-04-22 15:47:10
【问题描述】:

我想制作一个使用本机进度条呈现的 DataGridViewProgressBar。我目前有自定义绘制逻辑来完成此操作,但它看起来不太好。

【问题讨论】:

  • 其实我自己也想通了。我发现的方法非常简单,但与 Cody 发布的文章不同。我的自定义 DataGridViewCell 子类有一个 ProgressBar 成员变量(其重要属性在子类中转发)。在 Paint override 中,我将其绘制为位图,然后让单元格绘制位图。真正棘手的部分是为单元格设置动画。如果有更好的方法,我仍然愿意接受其他答案。一旦我的 8 小时自我回答暂停期到期,我将发布我的源代码。 :P

标签: c# .net winforms datagridview progress-bar


【解决方案1】:

好的,我至少找到了一种方法。我有一个 ProgressBar 成员变量,我将它绘制到单元格的绘制逻辑中的位图,然后让单元格绘制位图。真正棘手的部分是为单元格设置动画。可能有更好的方法,但这里有完整的可用代码:

//
// $Id: DataGridViewProgressBar.cs 2051 2010-06-15 18:39:13Z chambm $
//
//
// Original author: Jay Holman <jay.holman .@. vanderbilt.edu>
//
// Copyright 2011 Vanderbilt University - Nashville, TN 37232
//
// Licensed under the Apache License, Version 2.0 (the "License"); 
// you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at 
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
// See the License for the specific language governing permissions and 
// limitations under the License.
//


using System;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;


namespace CustomProgressCell
{
    public sealed class DataGridViewProgressColumn : DataGridViewColumn
    {
        public DataGridViewProgressColumn()
        {
            CellTemplate = new DataGridViewProgressCell();
        }
    }
}

namespace CustomProgressCell
{
    sealed class DataGridViewProgressCell : DataGridViewTextBoxCell
    {
        #region Public data accessors
        /// <summary>
        /// Gets or sets the progress bar's Maximum property.
        /// </summary>
        public int Maximum
        {
            get { return _progressBar.Maximum; }
            set { _progressBar.Maximum = value; startAnimation(); }
        }

        /// <summary>
        /// Gets or sets the progress bar's Minimum property.
        /// </summary>
        public int Minimum
        {
            get { return _progressBar.Minimum; }
            set { _progressBar.Minimum = value; startAnimation(); }
        }

        /// <summary>
        /// Gets or sets the text to display on top of the progress bar.
        /// </summary>
        public string Text
        {
            get { return _text; }
            set { _text = value; refresh(); }
        }

        /// <summary>
        /// Gets or sets the progress bar's drawing style.
        /// </summary>
        public ProgressBarStyle ProgressBarStyle
        {
            get { return _progressBar.Style; }
            set { _progressBar.Style = value; startAnimation(); }
        }
        #endregion

        /// <summary>
        /// Use these keywords in the Text property to their respective values in the text.
        /// </summary>
        public abstract class MessageSpecialValue
        {
            public const string Minimum = "<<Minimum>>";
            public const string Maximum = "<<Maximum>>";
            public const string CurrentValue = "<<CurrentValue>>";
        }

        #region Private member variables
        ProgressBar _progressBar;
        Timer _animationStepTimer;
        Timer _animationStopTimer;
        string _text;
        #endregion

        public DataGridViewProgressCell()
        {
            _progressBar = new ProgressBar()
            {
                Minimum = 0,
                Maximum = 100,
                Style = ProgressBarStyle.Continuous
            };

            _text = String.Format("{0} of {1}", MessageSpecialValue.CurrentValue, MessageSpecialValue.Maximum);

            ValueType = typeof(int);

            // repaint every 25 milliseconds while progress is active
            _animationStepTimer = new Timer { Interval = 25, Enabled = true };

            // stop repainting 1 second after progress becomes inactive
            _animationStopTimer = new Timer { Interval = 1000, Enabled = false };

            _animationStepTimer.Tick += (x, y) => { stopAnimation(); refresh(); };
            _animationStopTimer.Tick += (x, y) => { _animationStepTimer.Stop(); _animationStopTimer.Stop(); };
        }

        protected override object GetValue (int rowIndex)
        {
            return _progressBar.Value;
        }

        protected override bool SetValue (int rowIndex, object value)
        {
            if (value is int)
            {
                _progressBar.Value = (int) value;
                refresh();
                return true;
            }
            return false;
        }

        protected override void Paint (Graphics g, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
        {
            ReadOnly = true;

            // Draw the cell border
            base.Paint(g, clipBounds, cellBounds,
                       rowIndex, cellState, value, formattedValue, errorText,
                       cellStyle, advancedBorderStyle, DataGridViewPaintParts.Border);

            try
            {
                // Draw the ProgressBar to an in-memory bitmap
                Bitmap bmp = new Bitmap(cellBounds.Width, cellBounds.Height);
                Rectangle bmpBounds = new Rectangle(0, 0, cellBounds.Width, cellBounds.Height);
                _progressBar.Size = cellBounds.Size;
                _progressBar.DrawToBitmap(bmp, bmpBounds);

                // Draw the bitmap on the cell
                g.DrawImage(bmp, cellBounds);

                // Replace special value placeholders
                var editedMessage = _text.Replace(MessageSpecialValue.CurrentValue, Value.ToString())
                                         .Replace(MessageSpecialValue.Maximum, Maximum.ToString())
                                         .Replace(MessageSpecialValue.Minimum, Minimum.ToString());

                // Write text over bar
                base.Paint(g, clipBounds, cellBounds,
                           rowIndex, cellState, value, editedMessage, errorText,
                           cellStyle, advancedBorderStyle, DataGridViewPaintParts.ContentForeground);
            }
            catch (ArgumentOutOfRangeException)
            {
                // Row probably couldn't be accessed
            }
        }

        private void refresh ()
        {
            if (DataGridView != null) DataGridView.InvalidateCell(this);
        }

        private void startAnimation ()
        {
            if (_progressBar.Style == ProgressBarStyle.Marquee ||
                (_progressBar.Value > _progressBar.Minimum && _progressBar.Value < _progressBar.Maximum))
                _animationStepTimer.Start();
        }

        private void stopAnimation ()
        {
            if (_progressBar.Style != ProgressBarStyle.Marquee &&
                (_progressBar.Value == _progressBar.Minimum || _progressBar.Value == _progressBar.Maximum))
                _animationStopTimer.Start();
        }
    }
}

【讨论】:

  • 嗯,+1 用于自己解决问题。那么你真的是范德比尔特大学的杰伊霍尔曼吗?
  • Jay 是一位同事,他使用简单的矩形绘图(包括带有滚动矩形的选取框模式)编写了自定义进度单元的初始实现。实际上,他似乎采用了死锁的解决方案来解决这个问题(“Populating a DataGridView with Text and ProgressBars”)。 Jay 添加了棘手的动画部分,但我重写了足够多的内容,因此我很乐意称其为“原创”。
【解决方案2】:

您可以在DataGridView 单元格中托管您想要的任何控件。 MSDN 上有完整的示例:How to: Host Controls in Windows Forms DataGridView Cells

所以你可以只使用内置的ProgressBar control,它看起来就像原生的一样。


要回答您关于自定义 DataGridViewImageCell 的绘制逻辑以使其像进度条一样绘制的其他问题,这取决于您正在谈论的哪个本机进度条渲染。在 Windows Aero 之前使用的那个非常简单——它只是一个填充了系统突出显示颜色的实心矩形。重新实现该控件的绘制逻辑很简单。这就是the article Jay links to 试图做的事情。它并不完全正确 - 红色文本在绿色背景上看起来非常难看。如果您要以正确的方式执行此操作,则填充颜色将是系统突出显示颜色,百分比将是系统 WindowText 颜色。

但以 Aero 为主题的进度条看起来完全不同。对于初学者来说,它们是绿色的、渐变的,并且有跳动的效果。在 WinForms 中重现这并不是特别容易。很久以前,我浪费了很多时间来尝试一个项目,但我放弃了,因为它不完全一样。您可以从LinearGradientBrush 开始,但它看起来永远不会完全一样。而且你仍然不会有脉动和悸动的效果。除了严格的视觉外观之外,Aero 进度条还有漂亮的子步骤插值和其他动画效果,这些效果将被证明更加难以重新创建。老实说,这不值得付出努力,尤其是当使用 actual 进度条控件非常容易时。

如果您死心塌地,这里有一个示例控件可以帮助您入门:Vista Style Progress Bar in C#

确保当用户禁用 Aero 主题或在旧版本的 Windows(如 XP)上运行时,您的逻辑会回退到经典样式呈现。

【讨论】:

  • 您链接到的文章讨论了自定义编辑控件。所有 DataGridViewCells 共享相同的编辑控件 AFAIK。 ProgressBars 需要对单元格是唯一的。是的,这是我想要的以 Aero 为主题的酒吧(至少在 Vista/7 上)。 :)
  • @Matt:是的,因为大多数人想要在他们的 DataGridViewCells 中添加一个编辑控件,所以展示示例是合乎逻辑的。您仍然可以在其中托管您想要的任何控件。但我也用更多信息更新了我的答案;确保刷新页面!
  • 但是该示例没有显示如何在非编辑部分托管任何控件。自定义编辑控件继承自 DateTimePicker。这很简单。但是自定义单元格类必须继承自DataGridViewCell层次结构,那么ProgressBar控件又是从哪里来的呢?当然,如果有办法托管本机 ProgressBarControl 或调用其渲染代码,我当然不想重新发明绘画逻辑。 :) 您发布的最后一个链接确实是您警告过的重塑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-01
  • 2019-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-01
  • 2023-04-02
相关资源
最近更新 更多