volkihar
@volkihar

Как сделать, чтобы ProgressBar работал во время нагрузки на приложение?

Привет. Есть WPF приложение:
MainWindow.xaml

<Window.Resources>
        <Style x:Key="NormalStatusBar" TargetType="DockPanel">
            <Setter Property="Background" Value="#FF007ACC" />
        </Style>
        <Style x:Key="LoadDataStatusBar" TargetType="DockPanel">
            <Setter Property="Background" Value="#9333FF" />
        </Style>
        <Style x:Key="ErrorStatusBar" TargetType="DockPanel">
            <Setter Property="Background" Value="#eb4034" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>
        <Menu VerticalAlignment="Top">
            <MenuItem Header="File">
                <MenuItem
                    x:Name="LoadDataToDB"
                    Click="LoadDataToDB_Click"
                    Header="LoadDataToDB" />
            </MenuItem>
        </Menu>
        <DockPanel x:Name="StatusBar" Grid.Row="1" Style="{StaticResource LoadDataStatusBar}"> <!--Style меняться во время выполнения LoadDataInDB()-->
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock
                    x:Name="DataStatus"
                    Grid.Column="0"
                    Margin="10,0,0,0"
                    Foreground="White"
                    Text="Статус данных" />
                <DockPanel Grid.Column="1" HorizontalAlignment="Right">
                    <TextBlock
                        x:Name="CurrnetLine"
                        Margin="0,0,5,0"
                        Foreground="White"
                        Text="{Binding currentLine}" /> <!--должно меняться во время выполнения LoadDataInDB()-->
                    <TextBlock
                        Margin="0,0,5,0"
                        Foreground="White"
                        Text="/" />
                    <TextBlock
                        x:Name="MaxLines"
                        Margin="0,0,10,0"
                        Foreground="White"
                        Text="{Binding maxLines}" />
                </DockPanel>

            </Grid>
        </DockPanel>
    </Grid>


MainWIndow.cs:

public string maxLines { get; set; }
        public string currentLine { get; set; }

        Style normalBarStyle;
        Style LoadBarStyle;
        Style errorBarStyle;
        public MainWindow()
        {
            InitializeComponent();
            normalBarStyle = this.FindResource("NormalStatusBar") as Style;
            LoadBarStyle = this.FindResource("LoadDataStatusBar") as Style;
            errorBarStyle = this.FindResource("ErrorStatusBar") as Style;
            StatusBar.Style = normalBarStyle;
            DataContext = this;
        }
        private void LoadDataToDB_Click(object sender, RoutedEventArgs e)
        {
            StatusBar.Style = LoadBarStyle;
            Mouse.OverrideCursor = Cursors.Wait;
            Analyzer analyzer = new Analyzer();
            analyzer.LoadDataInDB(this);//вызываю метод который делает приложение не активным
            Mouse.OverrideCursor = Cursors.Arrow;
            StatusBar.Style = normalBarStyle;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void ChangeLineData()
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(maxLines)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(currentLine)));
        }


Analyzer.LoadDataInDB(MainWindow):
if (запрос к бд == true) {
 DataBase.ClearAll();
 DirectoryInfo info = new DirectoryInfo(@"path\Data\");
   DirectoryInfo[] dirs = info.GetDirectories();
   for (int r = 0; r < dirs.Length; r++) {
    var additionalLines = File.ReadAllLines(@"path\Data\" + dirs[r].Name + @"\
      Additional.txt "); ~ 10000строк
      var mainLines = File.ReadAllLines(@"pathr\Data\" + dirs[r].Name + @"\
       Main.txt "); ~ 18000строк
       for (int i = 0; i < mainLines.Length; i++) {
        DataMain data = new DataMain(); //обычный класс с переменными
        int markCounter = 0; //слова отделяются символом '!'
        string word = string.Empty;
        for (int j = 0; j < mainLines[i].Length; j++) {
         char symbol = mainLines[i][j];
         mainWindow.maxLines = mainLines.Length.ToString(); //общие количество линий
         mainWindow.currentLine = i.ToString(); //текущая линя
         mainWindow.ChangeLineData(); //вызываю метод для смены данных bining полей
         if (symbol == '!') { //если символ равен разделителю слов
          switch (markCounter) {
           case 2: //если второе слово, то заполнятся это: 
            data.Alpha = Convert.ToInt32(word);
            break;
           case 1: //и дальше тем же методом
            data.Bravo = long.Parse(word);
            break;
           case 0:
            data.Charlie = ConvertToInt(word);
            break;
           case 5:
            data.Delta = ConvertToInt(word);
            break;
           case 6:
            data.Echo = ConvertToInt(word);
            break;
           case 7:
            data.Foxtrot = ConvertToInt(word);
            break;
           case 10:
            data.Golf = ConvertToInt(word);
            break;
           case 11:
            data.Hotel = ConvertToInt(word);
            break;
          }
          word = string.Empty;//очистка переменной
          markCounter++;// +1 к счетчику разделителей 
         } else { //иначе добавляется символ к слову
          word += symbol.ToString();
         }
        }
        DataBase.AddMainData(data); // вызов хранимой процедуры с параметрами
       }
       for (int i = 0; i < additionalLines.Length; i++) {
        //тут все тоже самое только в DataAdditional полей в раза 3-4 больше.
       }
      }
     }

Алгоритм по парсингу данных работает хорошо. А вот ProgresBar сделать не могу. Когда вызываю LoadDataInDB(), приложение зависает на 20-25 секунд, даже передвинуть или закрыть его нельзя. Только после завершения метода maxLine/currentLine заменяются, но на максимальные. Я понимаю, что это из-за обработки и загрузки данных в БД. Но может возможно сделать так, чтобы пользователь мог видеть прогресс?
  • Вопрос задан
  • 169 просмотров
Решения вопроса 1
Casper-SC
@Casper-SC
Программист (.NET)
Сделал на основе вашего кода. Вообще нужно это всё писать немного иначе. Передавать ссылку в классы с алгоритмами на контролы - это очень плохая идея и сразу забудьте про такой подход.

В этом примере если два раза нажать на пункт меню, то запустится два цикла в поток(е/ах) из пула потоков. Это уже сами обработаете.

В данном примере Analyzer - это отдельная сущность, которая умеет уведомлять о каких-то своих внутренних событиях внешний мир посредством сигналов. Событие и есть тот самый сигнал, а обработать его сможет любое кол-во подписчиков. Событие можно воспринимать, как пины на плате Raspberry Pi, если к нему что-то подсоединить, то можно как-то реагировать на сигнал от пина, тут тоже самое.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ProgressBarExample
{
    internal class Analyzer
    {
        private readonly SynchronizationContext _synchronizationContext;

        public Analyzer()
        {
            // Если экземпляр класса будет создан в UI потоке,
            // то здесь будет контекст синхронизации UI потока, иначе пула потоков
            _synchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext();
        }

        public event EventHandler<AnalyzerEventArgs> ProgressChanged;

        public async Task<Data> LoadDataInDB()
        {
            var result = await Task.Run(async () =>
            {
                for (int i = 0; i < 100; i++)
                {
                    await Task.Delay(250);
                    OnProgressChanged(new AnalyzerEventArgs("line " + (i + 1), 100));
                }
                return new Data() { Text = "Данные " };
            });
            return result;
        }

        private void OnProgressChanged(AnalyzerEventArgs args)
        {
            // Перенаправляем выполнение в UI поток не ожидая пока отработает метод обработчик события.
            _synchronizationContext.Post(state =>
            {
                ProgressChanged?.Invoke(this, (AnalyzerEventArgs)state);
            }, args); // args передаётся в переменную state (грубо говоря)
        }
    }
}


namespace ProgressBarExample
{
    public class AnalyzerEventArgs
    {
        public int MaxLines { get; }

        public string CurrentLine { get; }

        public AnalyzerEventArgs(string currentLine, int maxLines)
        {
            CurrentLine = currentLine;
            MaxLines = maxLines;
        }
    }
}


namespace ProgressBarExample
{
    public class Data
    {
        public string Text { get; set; }
    }
}


using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;

namespace ProgressBarExample
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private readonly Analyzer _analyzer;
        private readonly Style _normalBarStyle;
        private readonly Style _loadBarStyle;
        private readonly Style _errorBarStyle;
        private string _maxLines;
        private string _currentLine;

        public string MaxLines
        {
            get { return _maxLines; }
            set
            {
                _maxLines = value;
                OnPropertyChanged();
            }
        }

        public string CurrentLine
        {
            get { return _currentLine; }
            set
            {
                _currentLine = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public MainWindow()
        {
            InitializeComponent();

            _normalBarStyle = FindResource("NormalStatusBar") as Style;
            _loadBarStyle = FindResource("LoadDataStatusBar") as Style;
            _errorBarStyle = FindResource("ErrorStatusBar") as Style;

            _statusBar.Style = _normalBarStyle;
            
            _analyzer = new Analyzer();
            _analyzer.ProgressChanged += OnAnalyzerProgressChanged;
        }

        private void OnAnalyzerProgressChanged(object sender, AnalyzerEventArgs args)
        {
            // Передавать каждый раз одно и тоже бессмысленно, сделаете сами как нужно
            MaxLines = args.MaxLines.ToString();
            CurrentLine = args.CurrentLine;
        }

        private async void LoadDataToDB_Click(object sender, RoutedEventArgs e)
        {
            _statusBar.Style = _loadBarStyle;
            Mouse.OverrideCursor = Cursors.Wait;

            try
            {
                var data = await _analyzer.LoadDataInDB(); 
                MessageBox.Show(data.Text);
            }
            finally
            {
                Mouse.OverrideCursor = Cursors.Arrow;
                _statusBar.Style = _normalBarStyle;
            }
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}


<Window
    x:Class="ProgressBarExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ProgressBarExample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    mc:Ignorable="d">
    <Window.Resources>
        <Style x:Key="NormalStatusBar" TargetType="DockPanel">
            <Setter Property="Background" Value="#FF007ACC" />
        </Style>
        <Style x:Key="LoadDataStatusBar" TargetType="DockPanel">
            <Setter Property="Background" Value="#9333FF" />
        </Style>
        <Style x:Key="ErrorStatusBar" TargetType="DockPanel">
            <Setter Property="Background" Value="#eb4034" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>
        <Menu VerticalAlignment="Top">
            <MenuItem Header="File">
                <MenuItem
                    x:Name="LoadDataToDB"
                    Click="LoadDataToDB_Click"
                    Header="LoadDataToDB" />
            </MenuItem>
        </Menu>
        <DockPanel
            x:Name="_statusBar"
            Grid.Row="1"
            Style="{StaticResource LoadDataStatusBar}">
            <!--  Style меняться во время выполнения LoadDataInDB()  -->
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock
                    Grid.Column="0"
                    Margin="10,0,0,0"
                    Foreground="White"
                    Text="Статус данных" />
                <DockPanel Grid.Column="1" HorizontalAlignment="Right">
                    <TextBlock
                        Margin="0,0,5,0"
                        Foreground="White"
                        Text="{Binding CurrentLine}" />
                    <!--  должно меняться во время выполнения LoadDataInDB()  -->
                    <TextBlock
                        Margin="0,0,5,0"
                        Foreground="White"
                        Text="/" />
                    <TextBlock
                        Margin="0,0,10,0"
                        Foreground="White"
                        Text="{Binding MaxLines}" />
                </DockPanel>

            </Grid>
        </DockPanel>
    </Grid>
</Window>
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
mindtester
@mindtester Куратор тега C#
http://iczin.su/hexagram_48
можно. только надо вынести обработку данный в отдельный от UI поток, а то блокировка получается

гуглите и обрящете прогресбар wpf
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы