WPF ProgressBar不使用INotifyPropertyChanged更新 [英] WPF ProgressBar Not Updating with INotifyPropertyChanged

查看:94
本文介绍了WPF ProgressBar不使用INotifyPropertyChanged更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有几个TextBox控件和一个ProgressBar的UserControl。 TextBox控件在绑定到的代码背后正确反映了属性。但是,ProgressBar不会响应属性更改。

I have a UserControl with several TextBox controls and a ProgressBar. The TextBox controls properly reflect the properties in codebehind to which they are bound. The ProgressBar does not respond to property change, however.

我的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>

及其背后的代码(非常缩写):

And its codebehind (very abbreviated):

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;
    }

}

我知道进度正在更改,因为我在NLog日志中看到它:

I know the value of Progress is changing because I see it in the NLog logs:

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

我不明白为什么在日志记录调用之前在设置器中触发 OnPropertyChanged 时为什么ProgressBar不更新。

I don't understand why the ProgressBar doesn't update when I fire OnPropertyChanged in the setter just before the logging call.

推荐答案

我以MVVM模式复制了按比例缩小版本的应用程序,并祝一切顺利。我使用此代码复制了您的用户控件...

I replicated a scaled down version of your app in an MVVM pattern and had good luck with it. I used this code to replicate your user control...

<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>

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

The only thing missing from that is your proprietary password control, which does not affect the solution.

我因此将此控件编码到MainWindow.xaml文件中。

I encoded this control into a MainWindow.xaml file thusly...

<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,但是这种方法适合快速试用和 指示性代码 。视图模型被设置为网格的数据上下文。 您的控件从网格继承了数据上下文。这就是xaml代码的结尾。除了对InitializeComponent的调用外,MainWindow.xaml.cs文件中没有任何代码隐藏(这是创建VM实例的地方)。

Note that the window resource definition includes a reference to a view model instance. Most people set up MVVM with dependency injection, but this approach is good for quick trials and Indicative Code. The view model is set as the Grid's data context. Your control inherits the data context from the grid. That's the end of the xaml code. There is no code-behind in the MainWindow.xaml.cs file other than the call to InitializeComponent (and that's where the VM instance gets created).

ViewModel类看起来像这样...

The ViewModel class looks like this...

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,尽管在您的应用程序中可能没有必要。

Most of the code in the view model looks like yours except there are no dependencies on UI elements. Everything is done via binding. I used a SynchronizationContext in the callback, although it may not be necessary in your application.

VM的构造函数在TPL线程上启动模型。该模型如下所示。

The constructor of the VM starts a model on a TPL thread. The model looks like this...

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在其自己的线程中进行更新。整个过程都按预期进行。

So putting it all together, the model is running in its own thread, and the UI is being updated on its own thread. The whole thing works as expected.

ProgressBar将其方式增加到100,并且UI在模型执行工作时将保持响应状态。这个答案不能解释为什么您的原始代码不起作用,但是我怀疑这与UI线程被饿死有关。您的完整日志历史记录证明了这一点,但是UI上没有任何变化。总体而言,此答案趋向于他人在其评论中提出的建议:即MVVM绑定方法提供了很多内容。

The ProgressBar will increment its way up to 100 and the UI will remain responsive while the model is doing its work. This answer does not explain why your original code does not work, but I suspect it has to do with the UI thread being starved out. This is evidenced by your complete log history, but nothing changing on the UI. Overall, this answer moves toward what others have suggested in their commentary: namely that the MVVM approach of binding has a lot to offer.

这篇关于WPF ProgressBar不使用INotifyPropertyChanged更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆