对DependencyProperty数据绑定进行故障排除

时间:2010-11-09 01:22:28

标签: silverlight data-binding

我为这个问题道歉了。通常情况下我会尽量避免要求其他人为我做调试,但是我已经进入了第二天这个问题,而且我承认我需要一些帮助。

我有一个Silverlight AudioVisualizer用户控件,它有一个FrameSource依赖属性,定义如下:

    public short[] FrameSource
    {
        get { return (short[])GetValue(FrameSourceProperty); }
        set { SetValue(FrameSourceProperty, value); }
    }

    public static readonly DependencyProperty FrameSourceProperty =
        DependencyProperty.Register("FrameSource", typeof(short[]), typeof(AudioVisualizer), new PropertyMetadata(OnFrameSourceChanged));

    private static void OnFrameSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        try
        {
            short[] source = e.NewValue as short[];
            AudioVisualizer instance = d as AudioVisualizer;
            if (source != null && source.Length > 0 && instance != null)
            {
                instance.RenderVisualization(source);
            }
        }
        catch (System.Exception ex)
        {
            ClientLogger.LogDebugMessage(ex.ToString());
        }
    }

我希望这个属性的源是一个由实现INotifyPropertyChanged的AudioStatistics类公开的short []数组,如下所示:

private short[] latestFrame;
public Array LatestFrame
{
    get
    {
        return latestFrame;
    }
    set
    {
        if (++FrameCount % ReportingInterval == 0 && value != null)
        {
            if (latestFrame == null || Buffer.ByteLength(latestFrame) != Buffer.ByteLength(latestFrame))
            {
                latestFrame = new short[Buffer.ByteLength(value) / sizeof(short)];
            }
            Buffer.BlockCopy(value, 0, latestFrame, 0, Buffer.ByteLength(value));
            OnPropertyChanged("LatestFrame");
        }
    }
}

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
    Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                    }
                });
}

(四个未成年人,但我相信上述代码无关紧要:(1)我只向音频可视化器发送一小部分音频帧,以保持CPU负载最小化;(2)音频处理总是在后台线程上进行,所以我需要将PropertyChanged事件编组到UI线程上;(3)最小化GC问题,我在分配期间将数据复制到缓冲区,而不是仅仅分配新的引用;(4)我将short [] latestFrame字段暴露为Array属性而不是short [],因为有时数据以byte []数组形式表示short,有时是short []数组,但是这没关系,因为#3。我也认为这段代码工作正常,因为其他绑定工作正常 - 见下文。仅包括完整性。)

我已经设置了这样的绑定:

    <ItemsControl x:Name="audioVisualizersListBox"  >
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <sdk:Label Content="{Binding Name}" />
                    <av:AudioVisualizer FrameSource="{Binding LatestFrame, Converter={StaticResource debugConverter}}"  Margin="4" Height="200" Width="300" VolumeFactor="3" />
                    <sdk:Label Content="{Binding LatestFrame, Converter={StaticResource debugShortsToStringConverter}}" />
                    <sdk:Label Content="{Binding FrameCount, StringFormat='Frames: {0}'}" />
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

ItemsControl绑定到MediaController类的ObservableCollection属性:

    AudioStatistics speakerStatistics = new AudioStatistics("Sent to Speaker");
    AudioStatistics microphoneStatistics = new AudioStatistics("Received from Microphone");
    AudioStatistics cancelledStatistics = new AudioStatistics("Echo Cancelled");
    AudioStats = new ObservableCollection<AudioStatistics>();
    AudioStats.Add(speakerStatistics);
    AudioStats.Add(microphoneStatistics);
    AudioStats.Add(cancelledStatistics);

DataContext的实际分配发生在我的主窗体的代码隐藏中,看起来就像你期望的那样:

audioVisualizersListBox.ItemsSource = mediaController.AudioStats;

除AudioVisualizer.FrameSource绑定之外的所有绑定都正常工作。例如,每次源对象的LatestFrame属性更改时,带有debugShortsToStringConverter转换器的标签都会更新,每次FrameCount更改时,绑定到FrameCount属性的标签也会更改。

问题是在我的AudioVisualizer控件中,OnFrameSourceChanged回调只被调用两次。在这两次调用之后,它再也不会被调用。它有点像“FrameSource”依赖属性没有正确注册INotifyPropertyChanged通知。我想我可以在OnFrameSourceChanged方法中以某种方式自己手动注册它们,但是我需要反思和什么不是,这似乎不对。

最后一个数据点:在某些时候之后我得到一个提交给FrameSource DependencyProperty的正确帧,我在VS调试窗口中得到了这个错误:

  

System.Windows.Data错误:BindingExpression路径错误:在'Alanta.Client.Controls.AudioVisualizer.AudioVisualizer''Alanta.Client.Controls.AudioVisualizer.AudioVisualizer'(HashCode = 63535124)上找不到'LatestFrame'属性。 BindingExpression:Path ='LatestFrame'DataItem ='Alanta.Client.Controls.AudioVisualizer.AudioVisualizer'(HashCode = 63535124); target元素是'Alanta.Client.Controls.AudioVisualizer.AudioVisualizer'(Name =''); target属性是'FrameSource'(类型'System.Int16 []')..

这是一个非常奇怪的错误消息,因为它似乎表明它正在尝试绑定到AudioVisualizer.LatestFrame属性,这当然不存在。 AudioStatistics类具有LatestFrame属性,但Audiovisualizer类仅具有FrameSource属性。非常令人困惑,但我已经仔细检查了我的绑定(和三重检查),它们似乎被正确定义。

对我在这里做错了什么的想法?这是我第一次尝试使用自己的依赖属性实现数据绑定,所以我很可能错过了一些明显的东西。

(11/9/10 - 编辑添加了我第一次遗漏的更多代码,因为它已经太长了。)

1 个答案:

答案 0 :(得分:0)

好的,我明白了。我犯了两个错误,一个是愚蠢的,一个是太聪明一半。

(1)在AudioVisualizer代码隐藏(我最初从其他地方借来的)的内部某处,它将DataContext重新分配给它自己。这是上面BindingExpression错误的来源。

(2)显然,DependencyProperty子系统试图了解它转发到其依赖项属性的INotifyPropertyChanged事件。除此之外,它似乎检查对象引用是否实际指向新对象。如果不是,则无需调用PropertyChangedCallback委托。因此,当我通过将新数据复制到其中来更新数组时,而不是更改引用,它不会像DependencyProperty子系统那样改变任何内容,因此它不会转发事件。

相关问题