我想学习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++;
如果有更好的方法,欢迎留言。
ObservableCollection
而不是在每次更改时替换整个集合。 - McGarnagle// 选项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