【问题标题】:WPF DependencyProperty throws InvalidOperationExceptionWPF DependencyProperty 抛出 InvalidOperationException
【发布时间】:2014-04-10 20:39:59
【问题描述】:

我正在尝试设置由 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,则不需要它。
  • 我认为我必须同时做这两件事。还是在这里学习。我使用 WPF 的第一周。
  • 实际上,VM 实现有两种可能性。第一个是实现 INotifyPropertyChanged 接口。第二种是从DependencyObject 继承,并将属性声明为Dependency 属性。第一个通常是最合适的。然而,ViewModel 不应该从像 UserControl 这样的 WPF 控件继承......好吧,除了某些特定情况。
  • 既然我们不能做多重继承,我想我将不得不坚持使用 INPC。
  • 显示您的 WCF 调用以更新进度条。

标签: c# wpf wcf wpf-controls wpf-4.0


【解决方案1】:

您需要通过 UI 线程更新您的 DepedencyProperty。使用:

Application.Current.Dispatcher.BeginInvoke(Action)

或者:

Application.Current.Dispatcher.Invoke(Action)

【讨论】:

  • 我试过了,this.Dispatcher.Invoke()。两者都对 ProgressBar 没有任何影响。
【解决方案2】:

我建议使用 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。 然后您的进度条将更新,并且您的应用在此过程中仍会响应。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-04
    • 2018-07-11
    相关资源
    最近更新 更多