Устранение неполадок привязки данных DependencyProperty - PullRequest
2 голосов
/ 09 ноября 2010

Мои извинения за вопрос так долго. Обычно я стараюсь не просить других людей делать отладку для меня, но у меня второй день по этой проблеме, и я признаю, что мне нужна помощь.

У меня есть пользовательский элемент управления 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());
        }
    }

Я бы хотел, чтобы источником этого свойства был короткий массив [], предоставляемый классом AudioStatistics, который реализует INotifyPropertyChanged, например:

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) Я отправляю только небольшое подмножество аудио кадров в аудио-визуализатор, чтобы сохранить нагрузку на процессор на минимум; (2) аудио обработка всегда происходит в фоновом потоке, поэтому мне нужно перенаправить событие PropertyChanged в поток пользовательского интерфейса; (3) чтобы минимизировать проблемы GC, я копирую данные в буфер во время назначения, а не просто назначаю новую ссылку ; (4) Я выставляю поле short [] latestFrame как свойство Array, а не как short [], потому что иногда данные поступают в виде массива byte [], представляющего шорты, а иногда массив 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 привязан к свойству ObservableCollection класса MediaController:

    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: свойство «LatestFrame» не найдено в «Alanta.Client.Controls.AudioVisualizer.AudioVisualizer» «Alanta.Client.Controls.AudioVisualizer.AudioVisualizer» (HashCode4). BindingExpression: Path = 'LatestFrame' DataItem = 'Alanta.Client.Controls.AudioVisualizer.AudioVisualizer' (HashCode = 63535124); целевым элементом является 'Alanta.Client.Controls.AudioVisualizer.AudioVisualizer' (Name = ''); Свойство target - 'FrameSource' (тип 'System.Int16 []') ..

Это очень странное сообщение об ошибке, потому что оно, похоже, указывает на то, что он пытается связать свойство AudioVisualizer.LatestFrame, которого, конечно, не существует. Класс AudioStatistics имеет свойство LatestFrame, но класс Audiovisualizer имеет только свойство FrameSource. Очень сбивает с толку, но я дважды проверил мои привязки (и проверил трижды), и они, кажется, определены правильно.

Есть мысли о том, что я здесь делаю не так? Я впервые пытаюсь реализовать привязку данных со своим собственным свойством зависимости, поэтому вполне возможно, что мне не хватает чего-то очевидного.

(11/9/10 - отредактировано, чтобы добавить больше кода, который я пропустил в первый раз, потому что он был уже слишком длинным.)

1 Ответ

0 голосов
/ 09 ноября 2010

ОК, я понял это. Я совершил две ошибки: одну глупую, другую половинчатую.

(1) Где-то в недрах кода AudioVisualizer (который я изначально позаимствовал где-то еще), он переназначал свой DataContext себе. Это было источником ошибки BindingExpression выше.

(2) По-видимому, подсистема DependencyProperty пытается понять, какие события INotifyPropertyChanged перенаправляют в ее свойства зависимостей. Помимо прочего, кажется, что проверяется, действительно ли ссылка на объект указывает на новый объект. Если это не так, он не будет вызывать делегат PropertyChangedCallback. Поэтому, когда я обновлял массив, копируя в него новые данные, а не менял ссылку, он не смотрел на подсистему DependencyProperty, как будто что-то изменилось, и, следовательно, он не переадресовывал событие.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...