填充面板作为进度条

时间:2018-11-28 15:43:59

标签: c# winforms progress-bar

我在winform itch上有一个带有按钮,2个标签和一个图像的面板。现在,我想从底部到顶部逐渐将面板背景的颜色更改为进度条。我尝试了一个带有组件的面板,第二个面板位于第一个面板的顶部,但位于组件的后面,然后逐渐扩大第二个面板的高度。但是组件的背景色使人联想起第一个面板的颜色。我试图将组件的背景色设置为透明。

有人建议实现这种效果吗?我所需要的不是面板,而是组件位于该区域的顶部,并且背景颜色会发生变化。

预先感谢

1 个答案:

答案 0 :(得分:2)

您希望使用Windows Forms应用程序实现的效果非常简单。有很多选项可以帮助您到达那里,但我将介绍Paint事件选项,在其中我们可以绘制一个自定义矩形以向用户显示进度。有两种方法可以有效地展示此路线:一种是使用Timer的简化方法,另一种是更深入的方法,但更适合于显示来自后台线程的进度。


Timer选项

使用Timer控件,我们可以用最少的代码来重现这种效果。您只需要PanelTimerfloat来跟踪进度。在FormLoad上启动计时器,在TimerTick上增加进度并使面板无效,在PanelPaint上绘制自定义进度:

private float progress = 0f;
private void Form1_Load(object sender, EventArgs e) => timer1.Start();
private void timer1_Tick(object sender, EventArgs e) {
    progress += 0.01f;
    if (progress >= 1.0f)
        progress = 0f;

    panel1.Invalidate();
}
private void panel1_Paint(object sender, PaintEventArgs e) {
    e.Graphics.FillRectangle(Brushes.Green, new Rectangle(0, 0, panel1.Width, (int)(panel1.Height * progress)));
}

如您所见,此方法的代码非常简单,易于解释。


事件选项

在后台线程上加载内容时,在更新GUI元素(例如Panel)时,事情会变得有些复杂。在这种情况下,我更喜欢使用SynchronizationContext在表单上引发事件,该事件将更新GUI元素而不会引发CrossThreadException。但是,在此特定示例中,事情相对简单,并且工作方式与上述相同,但涉及的代码更多。假设我们有一个class可以处理所有后台加载,并且都在单独的线程上进行;在这种情况下,我们有一个自定义事件,表单可以订阅该事件,SyncrhonizationContext会引发该事件,以便表单可以更新GUI。

public class DataLoader {

    #region Fields

    private bool loading = true;
    private Thread loadingThread;
    private SynchronizationContext loadingContext;

    #endregion

    #region Properties

    public float Progress { get; private set; } = 0f;

    #endregion

    #region Events

    public event EventHandler ObjectLoaded;
    private void OnObjectLoaded() => loadingContext.Post(new SendOrPostCallback(PostObjectLoaded), new EventArgs());
    private void PostObjectLoaded(object data) => ObjectLoaded?.Invoke(this, (EventArgs)data);

    #endregion

    #region Constructor(s)

    public DataLoader() {
        if (SynchronizationContext.Current != null)
            loadingContext = SynchronizationContext.Current;
        else
            loadingContext = new SynchronizationContext();

        loadingThread = new Thread(new ThreadStart(LoadData));
        loadingThread.IsBackground = true;
        loadingThread.Start();
    }

    #endregion


    #region Private Methods

    private void LoadData() {
        while (loading) {
            // Do some cool stuff to load data.
            CoolStuff();

            // Increment progress.
            Progress += 0.01f;
            if (Progress >= 1.0f)
                loading = false;

            // Now this object is loaded, raise event for subscribers.
            OnObjectLoaded();
        }
    }
    private void CoolStuff() {
        // Do cool loading stuff.
    }

    #endregion

}

现在,我们的加载类已构建并且正在加载对象以及所有有趣的爵士乐,我们可以将其添加到表单代码中,以执行计时器正在执行的相同操作。这样,我们订阅了ObjectLoaded事件,并遵循相同的过程。

private DataLoader loader;
private void Form1_Load(object sender, EventArgs e) {
    loader = new DataLoader();
    loader.ObjectLoaded += loader_ObjectLoaded;
}
private void loader_ObjectLoaded(object sender, EventArgs e) => panel1.Invalidate();
private void panel1_Paint(object sender, PaintEventArgs e) {
    e.Graphics.FillRectangle(Brushes.Green, new Rectangle(0, 0, panel1.Width, (int)(panel1.Height * loader.Progress)));
}

从底部填充矩形

要从底部填充矩形,您必须同时调整位置和高度。

int height = panel1.Height * progress;
Rectangle bounds = new Rectangle(0, panel1.height - height, panel1.Width, height);
e.Graphics.FillRectangle(Brushes.Green, bounds)

从右到左可以遵循相同的概念,从左到右可以遵循原始示例。

width = panel1.width * progress;
Rectangle bounds = new Rectangle(panel1.Width - width, 0, width, panel1.Height);

也有一些技巧可以从特定的位置开始,以获得一些烟雾和镜子类型的外观(例如,奇形异形的装载机),但我将留给您看看是否需要它们。

还要记住,您的Progress必须是float0之间的1值;否则,进行上述操作时,您必须除以100。在进度介于01之间的情况下,只需将100乘以用于显示即可。我总是发现保持01之间的进度比较容易,因为我经常在计算中使用它,而在显示时只使用一次。


达到100%的停止进度

从您的评论中,我相信您从示例中获得了进度递增,并且以0.01f递增。这不是执行此操作的正确方法,仅用于示例。

传统上,您需要计算任务总数(如果是文件,则为总大小),然后将完成的数量除以该总数即可获得进度。下面是一个基本示例,其中列出了要处理的对象。

private List<object> ObjectsToLoad = new List<object>();
private void DoCoolStuff() {
    int objectsLoaded = 0;
    foreach (object o in ObjectsToLoad) {
        // Process the object.
        Progress = (float)++objectsLoaded / (float)ObjectsToLoad.Count;;
        OnObjectLoaded();
    }
}

在这种情况下,您可以在OnObjectLoaded方法的while循环中删除对Load的调用,以防止重复发生事件。


如果您不熟悉所使用的任何类型,请随时查看MSDN上的文档;我在下面列出了最不常见的内容,如果您感兴趣的内容未列出,我深表歉意。您可以随时发表评论,我会添加它。

还要回答未来读者可能出现的问题;我使用loadingContext.Post而不是loadingContext.Send的原因是因为在这种情况下,我不相信后台线程真正关心GUI需要做什么,它只需要让GUI知道可以这样做。 Post告诉线程继续处理,而Send告诉线程在GUI线程上等待returnSend最适合GUI需要操纵后台线程发送的数据,然后将其发送回或更新后台线程上的某些内容。

祝您未来工作顺利!