WPF路径绑定到PointCollection不更新UI

6

我想学习MVVM风格的更新,但进展不顺。

我现在遇到了一个问题,即当点集合发生变化时绘制一个简单矩形的更新。在初始化时,UI更新正常,但是当点集合发生简单变化时,UI中的路径没有更新。

我添加了一些TextBlocks来确保Change事件被触发,但现在有点迷茫。

非常感谢任何帮助:

XAML:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ExampleGreg" x:Class="ExampleGreg.MainWindow"
        Title="MainWindow" Height="161.614" Width="324.087">
    <Grid x:Name="gridUser" MouseDown="click_MouseDown" >
        <Canvas x:Name="MeterCanvas" Margin="14,7,30,0" Background="#FFAFAFAF" Height="35" VerticalAlignment="Top">
            <Path Stroke="Black" StrokeThickness="2">
                <Path.Data>
                    <PathGeometry x:Name="geometry"/>
                </Path.Data>
            </Path>
            <Path Stroke="Black" StrokeThickness="2">
                <Path.Data>
                    <PathGeometry x:Name="polylinePwr">
                        <PathGeometry.Transform>
                            <ScaleTransform ScaleX="{Binding ActualWidth, ElementName=MeterCanvas}" ScaleY="{Binding ActualHeight, ElementName=MeterCanvas}" />
                        </PathGeometry.Transform>
                        <PathGeometry.Figures>
                            <PathFigure IsClosed ="True" StartPoint="{Binding Path=thePoints[0]}">
                                <PathFigure.Segments>
                                    <PathSegmentCollection>
                                        <PolyLineSegment Points="{Binding thePoints, UpdateSourceTrigger=PropertyChanged}" />
                                    </PathSegmentCollection>
                                </PathFigure.Segments>
                            </PathFigure>
                        </PathGeometry.Figures>
                    </PathGeometry>
                </Path.Data>
            </Path>
        </Canvas>
        <Label Content="{Binding thePoints[0]}" Margin="14,58,199.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
        <Label Content="{Binding thePoints[1]}" Margin="14,88,199.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
        <Label Content="{Binding thePoints[2]}" Margin="165,58,29.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
        <Label Content="{Binding thePoints[3]}" Margin="165,93,29.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
    </Grid>
</Window>

主窗口:

    public partial class MainWindow : Window
{
    private MainViewModel mvm;
    public MainWindow()
    {
        InitializeComponent();
        mvm = new MainViewModel();
        this.DataContext = mvm;

    }

    private void click_MouseDown(object sender, MouseButtonEventArgs e)
    {
        mvm.theText = mvm.theText + ".";
        mvm.ChangePoint(.4);
    }
}

模型视图:

    class MainViewModel : INotifyPropertyChanged
{
    private string _theText = "Initial";
    private PointCollection _points = new PointCollection();
    private PolyLineSegment segment;

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(String info)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(info));
        }
    }
    public MainViewModel()
    {
        ChangePoint(0.9);
    }

    public string theText
    {
        get { return _theText; }
        set
        {
            if (_theText != value)
            {
                _theText = value;
                OnPropertyChanged("theText");
            }
        }
    }
    public PointCollection thePoints
    {
        get
        { return _points; }
    }

    public void ChangePoint(double x)
    {
        _points.Clear();
        AddPoint(new Point(0.2, 0.2));
        AddPoint(new Point(0.2, 0.8));
        AddPoint(new Point(x, 0.8));
        AddPoint(new Point(x, 0.2));

        OnPropertyChanged("thePoints");
        _theText = _theText + "!";
        OnPropertyChanged("theText");


    }

    public void AddPoint(Point p)
    {
        _points.Add(p);
    }
}

感谢任何能起作用的建议 :) --根据下面的答案编辑-- 我添加了一个IValueConverter类:
    public class PointCollectionConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value.GetType() == typeof(ObservableCollection<Point>) && targetType == typeof(PointCollection))
        {
            var pointCollection = new PointCollection();
            foreach (var point in value as ObservableCollection<Point>)
                pointCollection.Add(point);
            return pointCollection;
        }
        return null;

    }

    public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null; //not needed
    }

    #endregion
}

并将ModelView更改为使用Observable Collection... 更新后的ModelView:

    class MainViewModel : INotifyPropertyChanged
{
    private string _theText = "Initial";
    private ObservableCollection<Point> _points;
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(String info)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(info));
        }
    }

    public MainViewModel()
    {
        _points = new ObservableCollection<Point>();
        _points.Add(new Point(0.2, 0.2));
        _points.Add(new Point(0.2, 0.8));
    }

    public string theText
    {
        get { return _theText; }
        set
        {
            if (_theText != value)
            {
                _theText = value;
                OnPropertyChanged("theText");
            }
        }
    }
    public ObservableCollection<Point> thePoints
    {
        get
        { return _points; }
    }

    double xAdder = 0;
    double y = 0.0;
    public void ChangePoint(double x)
    {
        y = y + .1;
        if (y > .9) { y = .1; xAdder += .1; }
        _points.Add(new Point(x+xAdder, y));
        OnPropertyChanged("thePoints");

        _theText = _theText + "!";
        OnPropertyChanged("theText");

    }

}

并更新了 XAML:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ExampleGreg"  x:Class="ExampleGreg.MainWindow"
    Title="MainWindow" Height="161.614" Width="324.087">
<Window.Resources>
    <local:PointCollectionConverter x:Key="pointCollectionConverter"/>
</Window.Resources>
<Grid x:Name="gridUser" MouseDown="click_MouseDown" >
    <Canvas x:Name="MeterCanvas" Margin="14,7,30,0" Background="#FFAFAFAF" Height="35" VerticalAlignment="Top">
        <Path Stroke="Black" StrokeThickness="2">
            <Path.Data>
                <PathGeometry x:Name="geometry"/>
            </Path.Data>
        </Path>
        <Path Stroke="Black" StrokeThickness="2">
            <Path.Data>
                <PathGeometry x:Name="polylinePwr">
                    <PathGeometry.Transform>
                        <ScaleTransform ScaleX="{Binding ActualWidth, ElementName=MeterCanvas}" ScaleY="{Binding ActualHeight, ElementName=MeterCanvas}" />
                    </PathGeometry.Transform>
                    <PathGeometry.Figures>
                        <PathFigure StartPoint="{Binding Path=thePoints[0], Converter={StaticResource pointCollectionConverter}}">
                            <PathFigure.Segments>
                                <PathSegmentCollection>
                                    <PolyLineSegment Points="{Binding thePoints, Converter={StaticResource pointCollectionConverter}}" />
                                </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathGeometry.Figures>
                </PathGeometry>
            </Path.Data>
        </Path>
    </Canvas>
    <Label Content="{Binding thePoints[0]}" Margin="14,58,199.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
    <Label Content="{Binding thePoints[1]}" Margin="14,88,199.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
    <Label Content="{Binding thePoints[2]}" Margin="165,58,29.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
    <Label Content="{Binding thePoints[3]}" Margin="165,93,29.6,0" VerticalAlignment="Top" Height="30" BorderThickness="1"/>
</Grid>

我不确定在每次ObservableCollection更改时,IValueConverter创建新的PointCollection是否会带来任何性能提升。

非常感谢大家的帮助。如果其他人遇到相同的问题,我想将完整的代码粘贴为可工作的代码。

编辑#2 - 在对使用IValueConverter的Observable与将PointCollection复制到新的PointCollection进行基准测试后,发现Point Collection似乎更快:

    // Option 2 - WAY Faster.
    // Just Use a Points Collection, copy it, and add a point.
    public void AddPoint2(Point pt)
    {
        PointCollection pc = new PointCollection(_points2);
        pc.Add(new Point(pt.X, pt.Y));
        _points2 = pc;
        OnPropertyChanged("thePoints2");
    }

调用自

            // 5000 point sinwave for testing
            foreach (Point pt in sinWave) mvm.AddPoint2(pt);
            x++;

如果有更好的方法,欢迎留言。

1个回答

2
尝试替换属性,而不是清除并重新填充它。
public void ChangePoint(double x)
{
    var newPoints = new PointCollection();
    newPoints.Add(new Point(0.2, 0.2));
    newPoints.Add(new Point(0.2, 0.8));
    newPoints.Add(new Point(x, 0.8));
    newPoints.Add(new Point(x, 0.2));

    OnPropertyChanged("thePoints");
    _theText = _theText + "!";
    OnPropertyChanged("theText");
}

似乎绑定有时会忽略“PropertyChanged”事件,如果对象引用相同。


我要跟进的问题是,这样做的性能影响是什么?我正在尝试更新一个App以提高效率,但如果我将其应用于我的点集合不断增长的情况下,是否真的会比当前的非mvvm模型更具优势呢?在这里,我只是不断地创建PathSegments,并将它们添加到PathSegmentCollection中。我想我得进行一些测试。 - GregG
@GregG 如果性能很重要,那么您应该尝试使用 ObservableCollection 而不是在每次更改时替换整个集合。 - McGarnagle
@GregG 就整体性能而言,这真的取决于情况。我猜你在做某种动画?那样的话,使用故事板/动画会更好吧? - McGarnagle
ObservableCollection<Point> 是我的第一次尝试。但是,在初始化时,UI甚至没有绘制出线条。您知道在这种情况下我是否只需要实现一个转换器吗?如果这是个愚蠢的问题,请原谅我。我正在努力理解这个问题。 - GregG
1
最近更新。我尝试使用IConverter对ObservableCollection进行基准测试,与将PointCollection复制到新的PointCollection并添加一个点进行比较。复制PointCollection的速度要快得多// 选项2-更快。 //只需使用Points Collection,将其复制并添加一个点。 public void AddPoint2(Point pt) { PointCollection pc = new PointCollection(_points2); pc.Add(new Point(pt.X, pt.Y)); _points2 = pc; OnPropertyChanged("thePoints2"); } - GregG
谢谢。在点集合上运行Clear方法,然后添加点并不会更新UI。即使在添加所有点之后强制触发属性更改事件也不行。每次创建全新的PointCollection才是解决方法!不过这似乎是WPF的一个bug :-) - mike gold

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