Заполнение панели как индикатор выполнения - PullRequest
0 голосов
/ 28 ноября 2018

У меня есть панель на зуд winform с кнопкой, 2 метки и изображение.Теперь я бы хотел постепенно менять цвет фона панели снизу вверх как индикатор выполнения.Я попытался с панелью с компонентами со второй панелью сверху первой, но позади компонентов, а затем постепенно увеличивал высоту второй панели.Но цвет фона компонента напоминает цвет первой панели.Я попытался установить прозрачный цвет фона компонента.

У кого-нибудь есть предложения для достижения этого эффекта?Это не обязательно должно быть с панелями. Все, что мне нужно, это чтобы компоненты оставались поверх области и чтобы цвет фона менялся.

Заранее спасибо

1 Ответ

0 голосов
/ 28 ноября 2018

Эффект, который вы хотите достичь, довольно прост с приложениями 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 необходимо манипулировать данными, отправляемыми фоновым потоком, а затем отправлять их обратно или обновлять что-либо в фоновом потоке.

Удачи вам в ваших будущих начинаниях!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...