Ram349 Ответов: 2

Индикатор занятости не отображается в WPF MVVM?


Всем Привет,
Я пытаюсь создать
Busy indicator
во время выполнения длинных задач, но
Busy indicator
не показывает
я использую приведенный ниже код

Код XAML-это :
<UserControl x:Class="ELT_Data_Extractor.View.DataBaseBrowser"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 

             xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"

             xmlns:viewmodel="clr-namespace:ELT_Data_Extractor.ViewModel"

             

             mc:Ignorable="d" 

             d:DesignHeight="600" d:DesignWidth="576">
    <UserControl.Resources>
        <viewmodel:ExtractInfoVM x:Key="vm"></viewmodel:ExtractInfoVM>

    </UserControl.Resources>
    <UserControl.DataContext>
        <viewmodel:ExtractInfoVM></viewmodel:ExtractInfoVM>
    </UserControl.DataContext>

    <Grid Height="540">
        <Grid.RowDefinitions>
            <RowDefinition Height="400" />
            <RowDefinition Height="5" />
            <RowDefinition Height="102" />
            <RowDefinition Height="53*" />
            
            
            

        </Grid.RowDefinitions>
        <StackPanel DataContext="{Binding Source={StaticResource vm}}">
            <Grid Height="400">
                <Grid.RowDefinitions>
                    <RowDefinition Height="33*" />
                    <RowDefinition Height="33*" />
                    <RowDefinition Height="33*" />
                    <RowDefinition Height="89*" />
                    <RowDefinition Height="41*" />
                    <RowDefinition Height="41*" />
                    <RowDefinition Height="41*" />
                    <RowDefinition Height="18*" />
                    <RowDefinition Height="35*" />
                    <RowDefinition Height="35*" />

                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="11" />
                    <ColumnDefinition Width="94" />
                    <ColumnDefinition Width="178*" />
                    <ColumnDefinition Width="261*" />
                    <ColumnDefinition Width="10*" />

                </Grid.ColumnDefinitions>
                
                
                
                <Label Content="Vendor:" Grid.Column="1"  Height="25" HorizontalAlignment="Stretch" Name="lblVendor" VerticalAlignment="Center" Grid.ColumnSpan="3" Margin="0,4"  />
                <ComboBox ItemsSource="{Binding VendorList}"  

                  SelectedValue="{Binding SelectedVendor}"  

                  Grid.Column="2" Height="23" HorizontalAlignment="Stretch" Name="cboVendor" VerticalAlignment="Center" Grid.ColumnSpan="2" Margin="0,5">
                    
                </ComboBox>

                <Label Content="Server:" Grid.Column="1"  Height="25" HorizontalAlignment="Stretch" Name="lblServer" VerticalAlignment="Center" Grid.ColumnSpan="3" Margin="0,1,0,7" Grid.Row="1" />
                <ComboBox Grid.Column="2" ItemsSource="{Binding Servers}"  

                  SelectedValue="{Binding SelectedServer}"

                  Height="23" HorizontalAlignment="Stretch" Name="cboServer" VerticalAlignment="Center" Grid.ColumnSpan="2" Margin="0,3,0,7" Grid.Row="1" />
               
                <Label Content="DataBase:" Grid.Column="1" Grid.Row="1" Height="25" HorizontalAlignment="Stretch" Name="lblDataBase" VerticalAlignment="Center" Grid.ColumnSpan="3" Margin="0,32,0,9" Grid.RowSpan="2" />
                <ComboBox Grid.Column="2" ItemsSource="{Binding Databases}"

                          SelectedValue="{Binding SelectedDatabase}"

                          Grid.Row="2" Height="23" HorizontalAlignment="Stretch" Name="cboDatabase" VerticalAlignment="Center" Margin="0,1,0,9" Grid.ColumnSpan="2" />
              
                <Label Content="Analysis:" Grid.Column="1" Grid.Row="2" Height="25" HorizontalAlignment="Stretch" Name="lblAnalysis" VerticalAlignment="Center" Grid.ColumnSpan="3" Margin="0,26,0,71" Grid.RowSpan="2" />
                <ComboBox Grid.Column="2" ItemsSource="{Binding Analysis}" 

                          SelectedValue="{Binding SelectedAnalasis}"

                        Grid.Row="2" Height="23" HorizontalAlignment="Stretch" Name="cboAnalysis"  VerticalAlignment="Center" Margin="0,29,0,70" Grid.RowSpan="2" Grid.ColumnSpan="2" />

                <TextBox Background="#FFEFECCD" Text="{Binding AnalysisInfo}"  Grid.Column="2" Grid.Row="3" HorizontalAlignment="Stretch"  Name="txtDesciptoin" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" TextWrapping="NoWrap" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" BorderThickness="2" Margin="0,24,0,0" Grid.ColumnSpan="2" />

                <Label Content="Perspective:" Grid.Column="1" Grid.Row="4" Height="25" HorizontalAlignment="Stretch" Name="lblperspective" VerticalAlignment="Center" Grid.ColumnSpan="3" Margin="0,8" />
                <ComboBox Grid.Column="2" ItemsSource="{Binding PerspectiveCodesList}" 

                          SelectedValue="{Binding SelectedPerspective}" 

                         Grid.Row="4" Height="23" HorizontalAlignment="Left" Name="cboPerspective" VerticalAlignment="Center" Width="186" Grid.ColumnSpan="2" Margin="0,9" />

                <Label Content="Level:" Grid.Column="1" Grid.Row="5" Height="25" HorizontalAlignment="Stretch" Name="lblLevel" VerticalAlignment="Center" Grid.ColumnSpan="3" Margin="0,8" />
                <!--IsEnabled="{Binding ElementName=cboVendor,Path=IsReadOnly}"-->
                <ComboBox Grid.Column="2" ItemsSource="{Binding LevelList}" 

                          SelectedValue="{Binding SelectedLevel}"  

                          DisplayMemberPath="LevelDescription"

                         Grid.Row="5" Height="23" HorizontalAlignment="Left" Name="cboLevel" VerticalAlignment="Center"  Width="186" Grid.ColumnSpan="2" Margin="0,9" />

                <Label Content="Loss Case:" Grid.Column="1" Grid.Row="6" Height="25" HorizontalAlignment="Stretch" Name="lblLossCase" VerticalAlignment="Center" Grid.ColumnSpan="3" Margin="0,8" />
                <ComboBox Grid.Column="2"  

                          Grid.Row="6" Height="23" HorizontalAlignment="Left" Name="cboLosscase" VerticalAlignment="Center" Width="186" Grid.ColumnSpan="2" Margin="0,9">
                    <ComboBox.Style>
                        <Style>
                            <Style.Triggers>
                                <DataTrigger 

                     Binding ="{Binding ElementName=cboVendor, Path=SelectedIndex}" 

                     Value="0">
                                    <Setter Property="ComboBox.IsEnabled" Value="false"/>
                                </DataTrigger>
                                <MultiDataTrigger>
                                    <MultiDataTrigger.Conditions>
                                        <Condition Binding="{Binding ElementName=cboVendor, Path=SelectedIndex}" Value="1" />
                                        
                                    </MultiDataTrigger.Conditions>
                                    <Setter Property="ComboBox.IsEnabled" Value="False" />
                                </MultiDataTrigger>
                            </Style.Triggers>
                        </Style>
                    </ComboBox.Style>
                </ComboBox>

                <Border BorderBrush="Silver" BorderThickness="1" Grid.Column="3" Grid.RowSpan="3" Grid.Row="4" HorizontalAlignment="left"  Name="border1" VerticalAlignment="Center" Width="257" Margin="17,11,0,3" Height="109">
                    <ListBox Name="lstReferenc" 

                             SelectedItem="{Binding Path=SelectedGranularity,UpdateSourceTrigger=PropertyChanged}" 

         ItemsSource="{Binding Granularitylist, NotifyOnSourceUpdated=True}" SelectionMode="Multiple" Margin="103,8,9,8" IsEnabled="{Binding Path=AllPropertiesValid}" >
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding DetailValue}"/>
                            </DataTemplate>
                        </ListBox.ItemTemplate>

                        <ListBox.ItemContainerStyle>
                            <Style TargetType="{x:Type ListBoxItem}">

                               
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding IsSelected}" Value="True">
                                        <Setter Property="Background" Value="Red" />
                                    </DataTrigger>
                                </Style.Triggers>
                                <Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
                            </Style>

                        </ListBox.ItemContainerStyle>

                    </ListBox>
                </Border>

                <Label Content="Granularity:" Grid.Column="3" Grid.Row="4" Height="25" HorizontalAlignment="Stretch" Margin="33,0,166,0" Name="label5" VerticalAlignment="Bottom"  />
                <Button Content="Select All"  Command="{Binding SelectAll}"

                        Grid.Column="3" Grid.Row="5" Height="23" HorizontalAlignment="Stretch" Margin="33,13,166,5" Name="btnSelectAll" VerticalAlignment="Center" Width="75" IsEnabled="{Binding Path=AllPropertiesValid}"  />
                <Button Content="Unselect All" Grid.Column="3" Grid.Row="6" Height="23" HorizontalAlignment="Stretch" Command="{Binding UnSelectAll}"

                        Name="btnUnselectAll" VerticalAlignment="Top" Width="75" Margin="33,8,166,0" IsEnabled="{Binding Path=AllPropertiesValid}" />

                <Label Content="File Name:" Grid.Column="1" Grid.Row="7" Height="25" HorizontalAlignment="Stretch" Name="label7" VerticalAlignment="Center" />
                <TextBox Grid.Row="7" Height="25" Name="txtName" Grid.Column="2" Text="{Binding FileName}" Grid.ColumnSpan="2" Margin="0,0,0,28" Grid.RowSpan="2" />

                <Label Content="Folder:" Grid.Column="1" Grid.Row="8" Height="25" HorizontalAlignment="Stretch" Name="label8" VerticalAlignment="Center" />
                <!--EJG comment: Bind Folder textbox to member property ("FilePath") in ExtractInfo object model (similar to FileName)-->
                <TextBox Grid.Row="8" Height="25" Name="txtPath" IsReadOnly="True" Grid.Column="2" Text="{Binding OutputPath,ValidatesOnDataErrors=True}" Grid.ColumnSpan="2" Margin="0,5" />
                <Button  Grid.Column="3" Content="(...)"  Command="{Binding FileBrowse}"

                         Grid.Row="8" HorizontalAlignment="Right" Name="btnBrowse" Width="40" VerticalAlignment="Center" Margin="0,7" />

                <Button Content="Generate" Command="{Binding GenerateReport}"

                        Grid.Column="3" Grid.Row="9" Height="25" HorizontalAlignment="Right" Name="btnGenerate" VerticalAlignment="Center" Width="100" Margin="0,5"  />
                <xctk:BusyIndicator IsBusy="{Binding BackgroundProcess}"  BusyContent="Extracting Data..."  >
                   
                </xctk:BusyIndicator>

            </Grid>
        </StackPanel>
        <GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" Margin="0,0,10,0" />
        <StackPanel Grid.Row="2" Grid.RowSpan="2" DataContext="{Binding Source={StaticResource vm}}" Margin="0,0,0,34">
            <DataGrid x:Name="grdfilelist" AutoGenerateColumns="False"  ItemsSource="{Binding ELTFileList}" Margin="10,2,8,51" Height="97" CanUserAddRows="False">
                <DataGrid.Columns>
                    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Button  Margin="3" Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" 

                						CommandParameter="{Binding ConverterParameter=parameter}">
                                        <ToolTipService.ToolTip>
                                            <ToolTip Content="Remove File" />
                                        </ToolTipService.ToolTip>
                                        <Image Height="12" Width="12"  Stretch="Uniform" Source="/Images/Delete.PNG" />
                                    </Button>
                                </Grid>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Header="FileName"  Binding="{Binding FileName}"/>
                    <DataGridTextColumn Header="Path" Binding="{Binding FilePath}" />
                </DataGrid.Columns>
            </DataGrid>

        </StackPanel>
    </Grid>
</UserControl>

Мой код C# является
private bool _backgroundprocess;

public bool BackgroundProcess
{
	get { return _backgroundprocess; }
	set
	{
		if (value != _backgroundprocess)
		{
			_backgroundprocess = value;
			OnPropertyChanged("BackgroundProcess");
		}
	}
}

public ObservableCollection<lossextractor.extractinfo> ExportData()
{
	backgroundprocess = true;
///My code is here 

	backgroundprocess = false;
}


Что я уже пробовал:

я пытался реализовать в главном окне его работу нормально

Graeme_Grant

Здорово, что вы разместили код, но здесь его слишком много!

2 Ответов

Рейтинг:
7

Richard Deeming

Цитата:
public ObservableCollection<lossextractor.extractinfo> ExportData()
{
    BackgroundProcess = true;
    ///My code is here 
    BackgroundProcess = false;
}

Вы не показали, где ExportData вызывается из, Но я предполагаю, что он вызван ICommand в некотором роде.

Проблема в том, что -Мой код здесь." часть работает в потоке пользовательского интерфейса. Этот поток блокируется до тех пор, пока код не завершится. Индикатор занятости никогда не получает возможности отображаться, потому что код, который изменяет его видимость, также выполняется в потоке пользовательского интерфейса и не выполняется до тех пор, пока ваш код не будет завершен.

Попробуйте переместить свой код в BackgroundWorker, так что пользовательский интерфейс имеет возможность обновиться:
Многопоточность с помощью BackgroundWorker[^]
private void StartExport()
{
    if (!exportBackgroundWorker.IsBusy)
    {
        BackgroundProcess = true;
        exportBackgroundWorker.RunWorkerAsync();
    }
}

private void exportBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Your code is here...
    e.Result = new ObservableCollection<lossextractor.extractinfo>(...);
}

private void exportBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    BackgroundProcess = false;
    
    if (e.Cancelled)
    {
        // Notify the user that the export was cancelled...
    }
    else if (e.Error != null)
    {
        // Display the error details to the user...
    }
    else
    {
        var exportedData = (ObservableCollection<lossextractor.extractinfo>)e.Result;
        // Do something with the data here...
    }
}


Ram349

Привет Ричард,
Спасибо за предоставленное решение,оно работает нормально, но все же я сталкиваюсь с проблемой "этот тип collectionview не поддерживает изменения в своей исходной коллекции из потока,отличного от потока диспетчера".

Richard Deeming

Вы получаете эту ошибку, когда пытаетесь обновить ObservableCollection из фонового потока, пока он привязан к элементу управления пользовательским интерфейсом.

Вот почему код, который я опубликовал, создал новую коллекцию в DoWork обработчик, и только обновил его в RunWorkerCompleted обработчик. Если вы попытаетесь обновить существующую коллекцию из DoWork Хэндлер, вы должны предпринять дополнительные шаги, чтобы заставить его работать.

Если вы используете .NET 4.5 или более позднюю версию, вы можете использовать Связывающие операции.Метода enablecollectionsynchronization[^] метод разрешения обновлений из фонового потока.

В противном случае вам придется использовать Диспетчер.Взывать[^] или Диспетчер.BeginInvoke[^] для обновления коллекции в потоке пользовательского интерфейса.

WPF 4.5: Observable Collection Cross-Thread Change Notification-Pete Brown's 10rem.net[^]

Ram349

Привет Ричард,
Я написал код, что когда-либо ваш общий, как это только я написал.но все же я получаю ту же проблему.

Ram349

Альтернативные времена его работы

Рейтинг:
2

Graeme_Grant

Согласно вашему XAML-коду, у вас есть два (2) экземпляра вашей ViewModel, установленные в трех (3) местах.

Однажды здесь привязанный к пользовательскому элементу управления:

<UserControl.DataContext>
    <viewmodel:ExtractInfoVM></viewmodel:ExtractInfoVM>
</UserControl.DataContext>

а здесь:
<StackPanel DataContext="{Binding Source={StaticResource vm}}">

а здесь:
<StackPanel Grid.Row="2" Grid.RowSpan="2" DataContext="{Binding Source={StaticResource vm}}" Margin="0,0,0,34">

Удалите последние два экземпляра, а также это ниже, и это должно сработать:
<UserControl.Resources>
	<viewmodel:ExtractInfoVM x:Key="vm"></viewmodel:ExtractInfoVM>
</UserControl.Resources>

Наконец, также убедитесь, что вы не установили DataContext в коде.

Обновление: Ричард Диминг предлагает использовать BackGroundWorker но в наши дни, забегая вперед, лучше научиться пользоваться Задача-асинхронная модель, основанная (нажмите)[^] как BackGroundWorker ограничивается определенным типом работы.

Вот пример длительной задачи с IsBusy государственный флаг. Этот пример оборачивает синхронный процесс и перемещает его из потока пользовательского интерфейса, чтобы остановить его блокировку и запустить в фоновом режиме. Это делается с помощью TaskCompletionSource(TResult) классовая система.Нарезание резьбы.Задачи)[^]. Я также использую MvvmLight[^] для RelayCommand, DispatherHelper, и ViewModelBase классы, чтобы сохранить пример кода простым.

1. ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using GalaSoft.MvvmLight.Threading;
using System.Threading.Tasks;

namespace LongRunningTask
{
    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
            => ExecuteTaskCommand = new RelayCommand(
                () => busyTask = StartExport(),
                () => !isBusy);

        private Task busyTask;
        public RelayCommand ExecuteTaskCommand { get; }

        private bool isBusy;
        public bool IsBusy
        {
            get => isBusy;
            set => Set(ref isBusy, value);
        }

        private async Task StartExport()
        {
            try
            {
                IsBusy = true;
                bool success = await ExportDataAsync().ConfigureAwait(false);
            }
            // ensure that no matter what, the busy state is cleared even if there were errors
            finally
            {
                // make sure we're on the UI thread...
                DispatcherHelper.CheckBeginInvokeOnUI(() =>
                {
                    IsBusy = false;
                    ExecuteTaskCommand.RaiseCanExecuteChanged();
                });
            }
        }

        private Task<bool> ExportDataAsync()
        {
            var tcs = new TaskCompletionSource<bool>();

            Task.Run(async () =>
            {
                // Do long running synchronous work here...
                await Task.Delay(3000).ConfigureAwait(false); // simulate a 3 second task

                // signal success!
                tcs.SetResult(true);
            }).ConfigureAwait(false);

            return tcs.Task;
        }
    }
}

2. MainWindow code-behind для инициализации DispatherHelper:
using GalaSoft.MvvmLight.Threading;
using System.Windows;

namespace LongRunningTask
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DispatcherHelper.Initialize();
        }
    }
}

3. MainWindow XAML:
<Window

    x:Class="LongRunningTask.MainWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"



    mc:Ignorable="d"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"



    xmlns:local="clr-namespace:LongRunningTask"



    Title="CODE PROJECT  -   LONG RUNNING TASK"

    Height="350" Width="525" WindowStartupLocation="CenterScreen">

    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

    <Grid>
        <Grid.Resources>
            <Style TargetType="Grid">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsBusy}" Value="True">
                        <Setter Property="Background" Value="Red"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Grid.Resources>

        <Button Content="Start Task" Padding="10,5"

                HorizontalAlignment="Center"

                VerticalAlignment="Center"

                Command="{Binding ExecuteTaskCommand}"/>
    </Grid>

</Window>

При нажатии на кнопку фон формы меняется на красный и кнопка отключается. Это должно указывать на состояние занятости. После завершения фон и кнопка возвращаются в свое нормальное состояние.


Ram349

Привет Greeme_Grant,
Спасибо, что ответили на мой пост,я добавил ниже код в свое окно. xaml. cs
DataContext = ELT_Data_Extractor.модель представления.ExtractInfoVM();
если что-то не так, не могли бы вы предоставить код datacontext в коде позади
Большое спасибо

Graeme_Grant

Почему? Установите DataContext только один раз в одном месте, а не в 3 - Теперь у вас есть 3 уникальных экземпляра ViewModel. Чтобы исправить это, удалите этот код и внесите изменения, как указано выше.