如何将加载动画和完成复选标记添加到 MVVM 中的绑定可观察集合?

How to add a loading animation and finished check mark to a bound observable collection in MVVM?

我有一个正在显示的文件列表,其中有一个用于输入文件名的文本框、一个可以让您 select 从文件资源管理器中访问一个文件的按钮,以及一个文本更新指示器,让用户知道文件何时正在加载以及何时完成加载。我想将文本指示器更改为 gif/jpg 指示器。我希望在文件开始加载时出现一个加载指示器,然后在文件加载完成时出现一个复选标记。我一直无法找到有关如何将 images/gifs 绑定到列表视图的任何信息。这可能吗?如果可以,该怎么做?有没有更好的方法来尝试处理此功能?

型号

using AuditEfficiencyMVVM.Helpers;
using AuditEfficiencyMVVM.Sources;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AuditEfficiencyMVVM.Model
{
    public class File : INotifyPropertyChanged
    {
        private Enums.FileType _type;
        private string _location;        
        private SourceBase _source;
        private Enums.FileLoadStatus _fileLoadStatus;
        private List<Enums.TestType> _tests = new List<Enums.TestType>();

        public Enums.FileType Type
        {
            get
            {
                return _type;
            }
            set
            {
                if (_type != value)
                {
                    _type = value;
                    RaisePropertyChanged("Type");
                }
            }
        }

        public string Location
        {
            get
            {
                return _location;
            }
            set
            {
                if (_location != value)
                {
                    _location = value;
                    RaisePropertyChanged("Location");
                }
            }
        }

        public List<Enums.TestType> Tests
        {
            get
            {
                return _tests;
            }
            set
            {
                if (_tests != value)
                {
                    _tests = value;
                }
            }
        }

        public SourceBase Source
        {
            get
            {
                return _source;
            }
            set
            {
                if (_source != value)
                {
                    _source = value;
                }
            }
        }

        public Enums.FileLoadStatus FileLoadStatus {
            get
            {
                return _fileLoadStatus;
            }
            set
            {
                if (_fileLoadStatus != value)
                {
                    _fileLoadStatus = value;
                    RaisePropertyChanged("FileLoadStatus");
                }
            }

            }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

视图模型

// Populate the List of Files on startup
public void LoadFiles()
        {
            ObservableCollection<Model.File> files = new ObservableCollection<Model.File>();

            foreach (Model.Test test in IncludedTests)
            {
                foreach (Enums.FileType type in test.ExpectedSources)
                {
                    Boolean containsType = false;
                    foreach (Model.File file in files)
                    {
                        if (file.Type == type)
                        {
                            containsType = true;
                            break;
                        }
                    }

                    if (!containsType)
                    {
                        files.Add(new Model.File { Type = type, Location = "", Source = null, FileLoadStatus = Enums.FileLoadStatus.NotStarted, Tests = new List<Enums.TestType> { test.Type } });
                    }
                    else
                    {
                        files.Where(t => t.Type == type).First().Tests.Add(test.Type);
                    }
                }
            }

            Files = files;
        }

// Logic to load the files on a button press
private async Task<SortedList<Enums.FileType, DataTable>> LoadFilesAsync()
        {
            try
            {
                SortedList<Enums.FileType, DataTable> fileList = new SortedList<Enums.FileType, DataTable>();

                foreach (var file in Files)
                {
                    // I want this to be something like LoadStatus = spinner.gif
                    file.FileLoadStatus = Enums.FileLoadStatus.InProgress;
                    fileList.Add(file.Type, await file.Source.LoadRecords(file));
                    // I want this to be something like LoadStatus = checkmark.jpg
                    file.FileLoadStatus = Enums.FileLoadStatus.Completed;
                }
                return fileList;
            }
            catch (Exception)
            {
                return null;
            }
        }

查看

<ListView Grid.Row="1" Grid.Column="1" Grid.RowSpan="4" Margin="10" ItemsSource="{Binding Path=Files}">
            <ListView.View>
                <GridView x:Name="file">
                    <GridViewColumn Header="File Type" DisplayMemberBinding="{Binding Type, Mode=OneWay}"/>
                    <GridViewColumn Header="File Location" Width="250">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBox Text="{Binding Path=Location, Mode=TwoWay}" Width="225"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>                    
                    <GridViewColumn>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Button Width="30" Height="20" Command="{Binding Path=DataContext.SelectFileCommand, 
                                                                        RelativeSource={RelativeSource AncestorType=ListView}}" 
                                                            CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, 
                                                                                AncestorType={x:Type GridViewRowPresenter}}, Path=DataContext}" Content="..."></Button>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Load Status" DisplayMemberBinding="{Binding FileLoadStatus, Mode=OneWay}"/>
                </GridView>
            </ListView.View>
        </ListView>

给你的视图模型一个 IsLoading 属性 和一个 IsLoaded 属性,都是布尔值。

为您的视图提供某种在 IsLoading 为真时可见的动画微调器控件,以及在 IsLoaded 为真时可见的某种 "loaded" 标记。我会将微调器叠加在列表视图的顶部:

<Grid>
    <ListView ...>
        <!-- ...stuff... -->
    </ListView>
    <Grid 
        Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}"
        >
        <!-- Spinner stuff -->
    </Grid>
</Grid>

LoadFileAsync()中适当设置IsLoadingIsLoaded

I have not been able to find anything on how to bind images/gifs to a list view.

你看起来有多努力?将 Image 控件放在单元格模板中,并将其 Source 属性 绑定到行项视图模型的 ImageSource 属性。 This may help

我发现了 this 教程,这就是我最终实现加载微调器的方法。

注意:我已经从 ListView 更改为 DataGrid 以显示数据,这就是为什么我的视图略有不同。

CircularProgressBar

<UserControl x:Class="AuditEfficiencyMVVM.View.CircularProgressBar"
             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:local="clr-namespace:AuditEfficiencyMVVM.View"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             Height="Auto" Width="Auto" Background="Transparent"
             IsVisibleChanged="HandleVisibleChanged">
    <Grid x:Name="LayoutRoot" Background="Transparent"
          ToolTip="Searching...."
          HorizontalAlignment="Center"
          VerticalAlignment="Center">
        <Canvas RenderTransformOrigin="0.5,0.5"
                HorizontalAlignment="Center"
             VerticalAlignment="Center" Width="120"
             Height="120" Loaded="HandleLoaded"
                Unloaded="HandleUnloaded"  >
            <Ellipse x:Name="C0" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="1.0"/>
            <Ellipse x:Name="C1" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.9"/>
            <Ellipse x:Name="C2" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.8"/>
            <Ellipse x:Name="C3" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.7"/>
            <Ellipse x:Name="C4" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.6"/>
            <Ellipse x:Name="C5" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.5"/>
            <Ellipse x:Name="C6" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.4"/>
            <Ellipse x:Name="C7" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.3"/>
            <Ellipse x:Name="C8" Width="20" Height="20"
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill"
                     Fill="Black" Opacity="0.2"/>
            <Canvas.RenderTransform>
                <RotateTransform x:Name="SpinnerRotate"
                     Angle="0" />
            </Canvas.RenderTransform>
        </Canvas>
    </Grid>
</UserControl>

查看

<DataGrid Grid.Row="1" Grid.ColumnSpan="3" Margin="10" ItemsSource="{Binding Path=Files}" SelectedValue="{Binding Path=SelectedUploadFile}" AutoGenerateColumns="False" CanUserAddRows="False">
    <DataGrid.Columns>
        <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Viewbox Width="25" Height="25"
                                        HorizontalAlignment="Center"
                                        VerticalAlignment="Center">
                                <local:CircularProgressBar Visibility="{Binding Path=Loading}" />
                            </Viewbox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

视图模型

public class AuditTests : INotifyPropertyChanged
{
    public ObservableCollection<FileMeta> Files { get; set; }

    private void AddFileRequested()
    {
        Files.Add(new FileMeta());
        SaveFilesCommand.RaiseCanExecuteChanged();
    }

    private void RemoveFileRequested()
    {
        Files.Remove(SelectedUploadFile);
        SaveFilesCommand.RaiseCanExecuteChanged();
    }

    public void InitializeData()
    {
        Files = new ObservableCollection<FileMeta>() { new FileMeta () };
    }
}

文件元

public partial class FileMeta : INotifyPropertyChanged
{
    private System.Windows.Visibility _loading;

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    public System.Windows.Visibility Loading
    {
        get
        {
            return _loading;
        }
        set
        {
            if (_loading != value)
            {
                _loading = value;
                RaisePropertyChanged("Loading");
            }
        }
    }
}