WPF DependencyProperty 抛出 InvalidOperationException

3

我正在尝试设置一个依赖属性,该属性由WCF回调线程更新。
MainWindow.xaml上有一个ProgressBar绑定到此属性:

MainWindow.xaml

<ProgressBar Name="ProgressBar" Value="{Binding Progress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />

MainWindow有一个DemoModule的实例,它被定义为:

DemoModule.xaml.cs

/// <summary>
///     Interaction logic for DemoModule.xaml
/// </summary>
public partial class DemoModule : UserControl, INotifyPropertyChanged
{
    public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", typeof(int), typeof(DemoModule));        
    public event ProgressEventHandler ProgressChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    public int Progress
    {
        get { return (int)GetValue(ProgressProperty); }
        set { SetValue(ProgressProperty, value); }  // setter throws InvalidOperationException "The calling thread cannot access this object because a different thread owns it"
    }

    /// <summary>
    ///     Initializes a new instance of the <see cref="DemoModule" /> class.
    /// </summary>
    public DemoModule()
    {
        InitializeComponent();
        ProgressChanged += OnProgressChanged;
    }

    /// <summary>
    /// Called when [progress changed].
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="args">The <see cref="ProgressChangedEventArgs" /> instance containing the event data.</param>
    public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
    {
        Debug.WriteLine("Current Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("Current Dispatcher Thread: {0}", Application.Current.Dispatcher.Thread.ManagedThreadId);

        if (ProgressChanged == null) return;

        Debug.WriteLine("ProgressChangedEventArgs.Current: " + args.Current);
        Progress = Convert.ToInt32(args.Current * 100);
        OnPropertyChanged("Progress");
    }

    /// <summary>
    /// Called when [property changed].
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        Trace.WriteLine("Property " + propertyName + " changed. Value = " + Progress);
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

}
Progress.set() 抛出异常是因为线程亲和性的原因。如何解决? 更新1 据说这是线程安全的,但没有效果:
public int Progress
{
    get
    {
        return Dispatcher.Invoke((() => (int)GetValue(ProgressProperty))); 
    }
    set
    {
        Dispatcher.BeginInvoke((Action)(() => SetValue(ProgressProperty, value)));
    }
 }

更新2

我的DemoModule.xaml.cs文件引用了一个客户端库,该库实现了WCF回调方法OnUpdateProgress:

InstallerAgentServiceClient.cs

public void OnUpdateProgress(double progress)
        {
            //Debug.WriteLine("Progress: " + progress*100 + "%");
            var args = new ProgressChangedEventArgs(progress, 1, "Installing");
            _installModule.OnProgressChanged(this, args);
        }

上面的_installModule对象是DemoModule的实例。 更新3 从WCF客户端库中删除[CallBackBehavior]属性后,似乎不再存在线程同步问题。我可以按照以下方式在MainWindow中更新进度条: DemoModule.xaml.cs
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
    Progress = Convert.ToInt32(args.Current * 100);
    var progressBar = Application.Current.MainWindow.FindName("ProgressBar") as ProgressBar;
    if (progressBar != null)
        progressBar.Value = Progress;
}

为什么要实现 INotifyPropertyChanged?如果你绑定到 DependencyProperties,那就不需要它了。 - Dmitry
我原以为我需要两者都做。我还在学习中。这是我使用WPF的第一周。 - Mark Richman
实际上,VM实现有两种可能性。第一种是实现INotifyPropertyChanged接口。第二种是从DependencyObject继承并将属性声明为Dependency属性。第一种通常是最合适的。然而,ViewModel不应该从WPF控件如UserControl继承...除了某些特定情况。 - Dmitry
既然我们不能进行多重继承,我想我只能坚持使用INPC。 - Mark Richman
请参见上面的更新2。 - Mark Richman
显示剩余4条评论
2个回答

6

您需要通过UI线程更新您的DependencyProperty。使用以下代码:

Application.Current.Dispatcher.BeginInvoke(Action)

或者:

Application.Current.Dispatcher.Invoke(Action)

我尝试了 this.Dispatcher.Invoke(),但是进度条没有任何效果。 - Mark Richman

0
我建议使用IProgress接口。对我来说非常好用,而且很容易使用。在您的progressbarVM中添加即可。
public double Actualprogress
{
get { return (double)GetValue(ActualprogressProperty); }
set { SetValue(ActualprogressProperty, value); }
}
public static readonly DependencyProperty ActualprogressProperty =
DependencyProperty.Register("Actualprogress", typeof(double), typeof(ProgressBar), 
new PropertyMetadata(0.0));

然后使用 await 作为异步任务调用您的方法,像这样:

var progress = new Progress<double>(progressPercent => 
progressBarVM.progressBar.Actualprogress = progressPercent);
Parser parser = new Parser();
ModelVMResult result = await Task.Run(() => parser.Parse(filename,progress));

然后在您的"解析"方法中只需执行:

float threshold = 0.0f;
for (int i = 0; i < count; i++)
{
if (i >= threshold)
{ progress.Report(prog += 1); threshold += count / 100.0f; }
this.Readline(reader, i);
}

当然,你需要将你的XAML ProgressBar.Value绑定到ProgressBarVM.Actualprogress。 然后你的进度条将会更新,而且在这个过程中你的应用程序仍然可以响应。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接