我在winform itch上有一个带有按钮,2个标签和一个图像的面板。现在,我想从底部到顶部逐渐将面板背景的颜色更改为进度条。我尝试了一个带有组件的面板,第二个面板位于第一个面板的顶部,但位于组件的后面,然后逐渐扩大第二个面板的高度。但是组件的背景色使人联想起第一个面板的颜色。我试图将组件的背景色设置为透明。
有人建议实现这种效果吗?我所需要的不是面板,而是组件位于该区域的顶部,并且背景颜色会发生变化。
预先感谢
答案 0 :(得分:2)
您希望使用Windows Forms应用程序实现的效果非常简单。有很多选项可以帮助您到达那里,但我将介绍Paint
事件选项,在其中我们可以绘制一个自定义矩形以向用户显示进度。有两种方法可以有效地展示此路线:一种是使用Timer
的简化方法,另一种是更深入的方法,但更适合于显示来自后台线程的进度。
Timer
选项使用Timer
控件,我们可以用最少的代码来重现这种效果。您只需要Panel
,Timer
和float
来跟踪进度。在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
必须是float
和0
之间的1
值;否则,进行上述操作时,您必须除以100
。在进度介于0
和1
之间的情况下,只需将100
乘以用于显示即可。我总是发现保持0
和1
之间的进度比较容易,因为我经常在计算中使用它,而在显示时只使用一次。
从您的评论中,我相信您从示例中获得了进度递增,并且以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线程上等待return
。 Send
最适合GUI需要操纵后台线程发送的数据,然后将其发送回或更新后台线程上的某些内容。
祝您未来工作顺利!