开发者

In C#, is there a way to call ProgressBar's paint logic to make it paint into a DataGridViewImageCell?

I want to make a DataGridViewProgressBar th开发者_如何学JAVAat uses the native progress bar rendering. I currently have custom paint logic to accomplish this but it doesn't look very nice.


OK I found at least one way to do it. I have a ProgressBar member variable and I draw it to a bitmap in the Cell's Paint logic and then have the cell draw the bitmap. The actually tricky part is animating the cell. There's probably a better way to do it, but here is complete, usable code for it:

//
// $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();
        }
    }
}


You can host any control that you want inside of a DataGridView cell. There's a complete sample available on MSDN: How to: Host Controls in Windows Forms DataGridView Cells

So you can just use the built-in ProgressBar control, and it will look as native as can be.


To answer your other question about customizing the paint logic of a DataGridViewImageCell to make it draw like a progress bar, it depends on which native progress bar rendering you're talking about. The one used up until Windows Aero was pretty simple—it was just a solid rectangle filled with the system highlight color. It's trivial to re-implement the painting logic for that control. That's what the article Jay links to attempts to do. It doesn't get it quite right—red text looks downright ugly on a green background. If you were going to do this the right way, the fill color would be the system highlight color and the percentage would be the system WindowText color.

But the Aero-themed progress bars look entirely different. For starters, they're green, gradient-filled, and have throbbing effects. That's not particularly easy to reproduce in WinForms. I wasted a lot of time trying for a project quite a while back, but I gave up because it just wasn't quite the same. You can get started with a LinearGradientBrush, but it'll never really look exactly the same. And you still won't have the pulsing and throbbing effects. And beyond the strictly visual appearance, the Aero progress bar got nifty sub-step interpolation and other animation effects that will prove even more difficult to re-create. My honest opinion is that it's not worth the effort, especially when using the actual progress bar control is so easy.

If you're dead-set, here's a sample control to get you started: Vista Style Progress Bar in C#.

Make sure that you have logic that falls back to the classic-style rendering when the user has the Aero theme disabled, or they're running on an older version of Windows (like XP).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜