WPF ProgressBar未使用INotifyPropertyChanged进行更新

本文关键字:更新 INotifyPropertyChanged ProgressBar 未使用 WPF | 更新日期: 2023-09-27 17:58:54

我有一个UserControl,里面有几个TextBox控件和一个ProgressBar。TextBox控件正确地反映了它们绑定到的代码绑定中的属性。但是,ProgressBar不响应属性更改。

我的XAML:

<UserControl
             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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule" 
             mc:Ignorable="d" 
             d:DesignHeight="600" d:DesignWidth="800">
    <Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
        <Canvas>
            <Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
            <TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
            <Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
            <TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
            <Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
            <TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
            <Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
            <Controls:BindablePasswordBox Password="{Binding DatabasePassword}" Height="23" Canvas.Left="160" Canvas.Top="96" Width="160"/>
            <ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
        </Canvas>
    </Grid>
</UserControl>

以及它的代码头(非常缩写):

public partial class MobileRecruiterModule : UserControl, INotifyPropertyChanged
{
    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
    private int _progress;
    public MobileRecruiterModule()
    {
        InitializeComponent();
        DataContext = this;
    }
    public string DatabaseServer { get; set; }
    public string DatabaseName { get; set; }
    public string DatabaseUsername { get; set; }
    public string DatabasePassword { get; set; }
    public int Progress
    {
        get { return _progress; }
        set
        {
            if (value == _progress) return;
            _progress = value;
            OnPropertyChanged("Progress");
            Logger.Trace("Progress.set() = " + _progress);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
    // This is called by an external class
    public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
    {
        Progress = args.ProgressPercentage;
    }
}

我知道Progress的值正在改变,因为我在NLog日志中看到了它:

2014-04-17 16:22:54.4068|TRACE|Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule|Progress.set() = 28

我不明白为什么在日志调用之前在setter中激发OnPropertyChanged时ProgressBar不更新。

WPF ProgressBar未使用INotifyPropertyChanged进行更新

我在MVVM模式中复制了你的应用程序的缩小版本,并很幸运。。。

<UserControl x:Class="ProgressBarBinding.Login"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
        <Canvas>
            <Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
            <TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
            <Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
            <TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
            <Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
            <TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
            <Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
            <ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
        </Canvas>
    </Grid>
</UserControl>

唯一缺少的是您专有的密码控制,它不会影响解决方案。

我把这个控件编码到MainWindow.xaml文件中。。。

<Window x:Class="ProgressBarBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:ProgressBarBinding"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <vm:ViewModel x:Key="ViewModel"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource ViewModel}">
        <vm:Login/>
    </Grid>
</Window> 

请注意,窗口资源定义包括对视图模型实例的引用。大多数人使用依赖性注入设置MVVM,但这种方法适用于快速试验和Indicative Code。视图模型被设置为网格的数据上下文您的控件从网格继承数据上下文xaml代码到此结束。MainWindow.xaml.cs文件中除了调用InitializeComponent之外没有任何代码隐藏(这就是创建VM实例的地方)。

ViewModel类如下所示。。。

public class ViewModel : INotifyPropertyChanged
{
    private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
    public ViewModel()
    {
        DatabaseServer = "AnyServer";
        DatabaseName = "Any name";
        Model m = new Model();
        Task.Run(() => m.DoWork(this));
    }
    public string DatabaseServer { get; set; }
    public string DatabaseName { get; set; }
    public string DatabaseUsername { get; set; }
    public string DatabasePassword { get; set; }
    private int _progress;
    public int Progress
    {
        get { return _progress; }
        set
        {
            if (value == _progress) return;
            _progress = value;
            OnPropertyChanged("Progress");
            Console.WriteLine(@"Progress.set() = " + _progress);
        }
    }
    // This is called by an external class
    public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
    {
        _synchronizationContext.Send(delegate { Progress = args.ProgressPercentage; }, null);
    }
    #region INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string name)
    {
        var handler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
    #endregion
}

视图模型中的大多数代码看起来与您的代码相似,只是没有对UI元素的依赖关系。一切都是通过绑定完成的。我在回调中使用了SynchronizationContext,尽管在您的应用程序中可能没有必要。

VM的构造函数在TPL线程上启动一个模型。模型看起来是这样的。。。

public class Model
{
    public void DoWork(ViewModel vm)
    {
        int progressPercentage = 0;
        for (int i = 0; i < 100000; i++)
        {
            vm.OnProgressChanged(this, new ProgressChangedEventArgs(progressPercentage, null));
            if (i%1000 == 0)
            {
                ++progressPercentage;
            }
        }
    }
}

因此,将所有这些放在一起,模型在自己的线程中运行,UI在自己的螺纹上更新。整个事情如预期的那样进行。

ProgressBar将增加到100,并且在模型工作时UI将保持响应。这个答案并不能解释为什么您的原始代码不起作用,但我怀疑这与UI线程被耗尽有关。这可以通过您完整的日志历史记录来证明,但UI上没有任何变化。总的来说,这个答案正朝着其他人在评论中提出的方向发展:即MVVM绑定方法可以提供很多东西。

由于您在UserControl中,因此需要显式地为其命名,并在绑定时使用ElementName标记,如下所示:

<UserControl
             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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule" 
             mc:Ignorable="d" 
             d:DesignHeight="600" d:DesignWidth="800" x:Name="MyControl">
    <Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
        <Canvas>
            <ProgressBar Name="ProgressBar" Value="{Binding Progress, ElementName=MyControl}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
        </Canvas>
    </Grid>
</UserControl>

如果您有Actual control,为什么需要Binding。由于您没有在MVVM中执行此操作,请立即致电ProgressBar

ProgressBar.Dispatcher.Invoke(() => ProgressBar.Value = Progress = args.ProgressPercentage);

很抱歉,如果View的类中的所有属性/控件都可以访问,那么我看不出Binding的好处。

如果您实现了MVVM,绑定将更加有用和强大。