Эффект, который вы хотите достичь, довольно прост с приложениями 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)));
}
Как видите, код для этогоМетод довольно тривиален и легко объясним.
Опция события
При загрузке вещей в фоновом потоке, все может стать немного сложнее при обновлении элементов графического интерфейса, таких как Panel
.В этом случае я предпочитаю использовать SynchronizationContext
для вызова событий в форме, которые будут обновлять элементы графического интерфейса без повышения CrossThreadException
s.В этом конкретном примере, однако, все относительно просто и работает так же, как и выше, но с большим количеством кода.Скажем, у нас есть 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
, так как я часто использую его для вычислений и только один раз для отображения.
Остановка прогресса при 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
, заключается в том, что в этом случае я не верил, что фоновый поток действительно заботился о том, что нужно сделать с графическим интерфейсом, он просто должен был сообщить об этом графическому интерфейсу.Post
указывает потоку продолжить обработку, в то время как Send
говорит потоку ждать return
из потока GUI.Send
лучше всего подходит, когда GUI необходимо манипулировать данными, отправляемыми фоновым потоком, а затем отправлять их обратно или обновлять что-либо в фоновом потоке.
Удачи вам в ваших будущих начинаниях!