Привязка точек полилинии к ObservableCollection не работает

Я застрял в привязке точек полилинии к ObservableCollection(Of Point):

<UserControl
x:Class="GL.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="640" d:DesignWidth="840">
<Grid x:Name="LayoutRoot" Background="#ff444444">
    <Canvas Background="#333333" Width="800" Height="600">
        <Polyline x:Name="Linie" Stroke="Yellow" StrokeThickness="2" Canvas.Left="0" Canvas.Top="0" Width="800" Height="600" Fill="Gray" Points="{Binding Punkte}">
        </Polyline>
    </Canvas>
    <TextBlock Height="55" Name="tb" Foreground="White" FontSize="{Binding Path=TS}" Text="JUST A TEST!" />
    <Button Content="Add Point" Height="23" HorizontalAlignment="Left" Margin="745,617,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />
</Grid>

Here's the code behind:

Imports System.Windows
Imports System.Windows.Media
Imports System.Collections.ObjectModel

Partial Public Class MainPage
    Inherits UserControl

    Dim r As New Random(345)
    Private _punkte As New ObservableCollection(Of Point)
    Public Property Punkte As ObservableCollection(Of Point)
        Get
            Return _punkte
        End Get
        Set(value As ObservableCollection(Of Point))
            _punkte = value
            SetValue(Punkte_DP, _punkte)
        End Set
    End Property

    Private _ts As Integer
    Public Property TS As Integer
        Get
            Return _ts
        End Get
        Set(value As Integer)
            _ts = value
            SetValue(TS_DP, _ts)
        End Set
    End Property

    Public Punkte_DP As DependencyProperty = DependencyProperty.Register("Punkte", GetType(ObservableCollection(Of Point)), GetType(MainPage), New PropertyMetadata(New ObservableCollection(Of Point)))
    Public TS_DP As DependencyProperty = DependencyProperty.Register("TS", GetType(Integer), GetType(MainPage), New PropertyMetadata(New Integer))

    Public Sub New()

        Me.DataContext = Me
        InitializeComponent()

        Linie.DataContext = Me.Punkte
        Punkte.Add(New Point(100, 100))
        Punkte.Add(New Point(700, 300))

        TS = 25
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
        Punkte.Add(New Point(r.Next(0, 600), r.Next(0, 600)))
    End Sub
End Class

Когда я запускаю это, FontSize обновляется, но нет ни одной точки, соответственно. чертится линия. Коллекция увеличивается при каждом нажатии кнопки, но ничего не происходит.

Какого черта мне здесь не хватает? Спасибо за вашу помощь!

С уважением, Роб


person Rob    schedule 13.12.2012    source источник


Ответы (1)


Причина, по которой точки в многоугольнике не обновляются, заключается в том, что Polygon.Points принимает значение типа PointCollection, и что Silverlight не может самостоятельно преобразовать ObservableCollection<Point> в PointCollection.

Что вам нужно сделать, так это добавить конвертер, преобразующий ObservableCollection<Point> в PointCollection. Что-то вроде следующего должно делать:

public class ObservableCollectionToPointCollectionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var points = value as ObservableCollection<Point>;
        if (points == null)
        {
            return null;
        }

        var collection = new PointCollection();
        foreach (Point point in points)
        {
            collection.Add(point);
        }

        return collection;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // Not needed for one-way bindings.
        throw new NotImplementedException();
    }
}

В качестве упражнения я оставлю вам возможность преобразовать это в VB.NET.

Чтобы подключить этот преобразователь, добавьте объявление пространства имен, например

xmlns:myns="clr-namespace:YourNamespaceContainingTheConverter"

к корневому тегу <UserControl> добавьте

<UserControl.Resources>
    <myns:ObservableCollectionToPointCollectionConverter x:Key="converter" />
</UserControl.Resources>

после конца этого тега, но перед <Grid>, и измените привязку Points в вашем Polygon на

Points="{Binding Punkte, Converter={StaticResource converter}}"

Однако было несколько других проблем, с которыми я столкнулся в вашем коде.

Во-первых, я получил несколько ошибок «Значение не попадает в ожидаемый диапазон», когда впервые запустил ваш код. Это оказалось связано с тем, что TextBlock.FontSize должен иметь положительное значение. Я переместил инициализацию TS до назначения Me.DataContext, и эти ошибки исчезли.

Во-вторых, строка Linie.DataContext = Me.Punkte неверна. Это устанавливает DataContext Polygon на ваш ObservableCollection из Points. Привязка {Binding Punkte} в свойстве Points многоугольника указывает Silverlight искать коллекцию в свойстве с именем Punkte контекста данных, то есть ObservableCollection из Points. Это не удастся, так как класс ObservableCollection не имеет свойства с именем Punkte. Эту строку следует удалить — Polygon унаследует свой контекст данных, который имеет свойство с именем Punkte, от своего родителя.

В-третьих, я заменил использование вами свойств зависимостей в MainPage.xaml.vb «обычными» свойствами CLR и заставил MainPage реализовать INotifyPropertyChanged. Тогда свойства выглядели следующим образом:

    public ObservableCollection<Point> Punkte
    {
        get { return _punkte; }
        set
        {
            _punkte = value;
            FirePropertyChanged("Punkte");
        }
    }

    public double TS
    {
        get { return _ts; }
        set
        {
            _ts = value;
            FirePropertyChanged("TS");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void FirePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Опять же, не стесняйтесь конвертировать это в VB.NET.

Последним изменением, которое я внес, было добавление вызова FirePropertyChanged("Punkte") в обработчик событий Button1_Click. Это позволяет Silverlight узнать, что наблюдаемая коллекция точек (и, следовательно, преобразованная коллекция PointCollection) изменилась и должна быть обновлена ​​пользовательским интерфейсом.

Как правило, использования ObservableCollections для коллекций в модели представления достаточно, чтобы уведомление об изменениях работало. Однако в этом случае это работает не так хорошо, потому что ObservableCollection преобразуется в другой объект перед передачей на уровень пользовательского интерфейса. В результате события CollectionChanged, запущенные ObservableCollection, не прослушиваются. Это не проблема вашего кода как такового, это скорее ограничение фреймворка. Я посмотрел, можно ли написать подкласс PointCollection, который также реализует INotifyCollectionChanged, но это невозможно, поскольку PointCollection равно sealed.

Вместо запуска этого события только для Add может быть лучше обработать событие CollectionChanged ObservableCollection в вашем классе MainPage и вызвать обработчик этого события FirePropertyChanged("Punkte"). Таким образом, полигон будет обновляться со всеми изменениями в коллекции.

person Luke Woodward    schedule 16.12.2012