WPF进度不与INotifyPropertyChanged的更新(WPF ProgressBar N

2019-09-28 06:42发布

我有几个TextBox控件和进度一个用户控件。 该TextBox控件正确反映代码隐藏属性它们的约束。 该进度条没有财产变化做出反应,但是。

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

我不明白为什么当我火进度不更新OnPropertyChanged只是日志调用之前的二传手。

Answer 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 。 视图模型设置为网格的数据上下文。 你的控制继承了网格数据上下文。 这就是XAML代码的结束。 有没有代码隐藏在MainWindow.xaml.cs文件比InitializeComponent调用其他的(这就是虚拟机实例被创建在哪里)。

该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,虽然它可能不会在你的应用程序是必要的。

虚拟机的构造开始对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正在对自己的线程更新。 整个事情按预期工作。

该进度条将增加它的方式,以100和而模型在做其工作的UI会保持响应。 这个答案并不能解释为什么你原来的代码不工作,但我怀疑它做的UI线程被饿死了。 这是由您完整的日志历史证明,但在用户界面上没有什么变化。 总体来说,这个答案移向在他们的评论建议他人:即结合的MVVM方法有很多提供。



Answer 2:

既然你在一个用户控件的时候,你需要绑定时,像这样明确地给它一个名称,使用的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>


Answer 3:

为什么你需要Binding ,如果你有Actual control 。 既然你是不是在做MVVM只是调用ProgressBar的时候了。

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

很抱歉,但我看不到的利益Binding ,如果所有属性/控制在你访问View的类。

结合更加有用,如果您实现强大的MVVM



文章来源: WPF ProgressBar Not Updating with INotifyPropertyChanged