【问题标题】:WPF ProgressBar Not Updating with INotifyPropertyChangedWPF ProgressBar 未使用 INotifyPropertyChanged 更新
【发布时间】:2014-06-02 07:13:03
【问题描述】:

我有一个带有多个 TextBox 控件和一个 ProgressBar 的 UserControl。 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 没有更新。

【问题讨论】:

  • 如何更新您的Progress 属性?
  • 通过我自己的自定义事件处理程序。我已经更新了上面的示例。
  • 您是否尝试在调度程序线程中执行更新? DB 活动可能让 pb 有机会更新 UI。
  • @GarryVass 不需要。 WPF 足够聪明,可以将其编组到 UI 线程。
  • @MarkRichman,如果 UI 被阻止,它就会变得非常相关。

标签: c# wpf xaml datacontext inotifypropertychanged


【解决方案1】:

我以 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。视图模型被设置为 Grid 的数据上下文。 您的控件从网格继承数据上下文。 这是 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 方法可以提供很多东西。

【讨论】:

    【解决方案2】:

    由于你在一个 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>
    

    【讨论】:

    • 不幸的是,这没有效果,我不得不删除 ElementName="MyControl" 周围的引号以使其编译:Value="{Binding Progress, ElementName=MyControl}"
    • 您确定在 UserControl 声明中设置了 x:Name 标签吗?
    • 我确实做到了...您能解释一下为什么 UserControl 需要这样做吗?我是 WPF 新手,所以我仍然在 Winforms 中思考。
    • 并且 OP 正在做 DataContext = this; 所以绑定到自身应该在不使用 elementname 的情况下工作
    【解决方案3】:

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

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

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

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

    【讨论】:

    • 我计划实现 MVVM,但这个应用程序是一个原型。我只是想了解为什么绑定不起作用。
    • 其余部分也在使用绑定,为什么要以不同的方式进行?
    • @MarkRichman 通过外部类,你是什么意思?那是在不同的线程中运行吗?你确定它并没有真正更新进度条吗?它会卡在值 0 并关闭吗?
    • @JohnGardner 因为如果以正确的方式实现,那么他就不会遇到这种奇怪的情况。使用不应该用于此类实例的概念是不正确的。
    • @MarkRichman 对我来说,似乎有些东西正在占用 UI,它还没有时间更新 ProgressBar,只是在 100 时更新它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多