WPF 路径绑定到 PointCollection 不更新 UI

Gre*_*egG 6 c# wpf xaml binding mvvm

我正在尝试学习 MVVM 样式更新。不会那么游泳。

我坚持更新一个简单的矩形,该矩形基于对点集合的更改而绘制。在初始化时,UI 会更新,但在对 Point Collection 进行简单更改时,UI 中的 Path 不会更新。

我添加了一些 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>
Run Code Online (Sandbox Code Playgroud)

主窗口:

    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);
    }
}
Run Code Online (Sandbox Code Playgroud)

模型视图:

    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);
    }
}
Run Code Online (Sandbox Code Playgroud)

感谢您的任何建议(工作:))

--根据下面的答案进行编辑-- 我添加了一个 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
}
Run Code Online (Sandbox Code Playgroud)

并将 ModelView 更改为使用 Observable 集合...更新 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");

    }

}
Run Code Online (Sandbox Code Playgroud)

并更新了 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>
Run Code Online (Sandbox Code Playgroud)

我不确定我是否会获得任何性能改进,因为 IValueConverter 创建了一个新的 PointCollection 每个 ObservableCollection 都发生了变化。

谢谢大家帮我解决这个问题。如果任何其他人遇到同样的事情,我想粘贴完整的代码作为工作。

编辑 #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");
    }
Run Code Online (Sandbox Code Playgroud)

来自

            // 5000 point sinwave for testing
            foreach (Point pt in sinWave) mvm.AddPoint2(pt);
            x++;
Run Code Online (Sandbox Code Playgroud)

如果有更好的方法,欢迎评论。

McG*_*gle 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");
}
Run Code Online (Sandbox Code Playgroud)

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