I am trying to learn MVVM style updates. Not going so swimmingly.
I'm stuck on updating a simple rectangle that is drawn based upon a change to a Point Collection. On initialization, the UI Updates, but upon a simple change to the Point Collection, the Path is not updated in the UI.
I added a few TextBlocks to make sure the Change event is getting fired, but I am a little lost at this point.
Any help appreciated:
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>
MainWindow:
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);
}
}
ModelView:
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);
}
}
Thanks for any suggestions (that work :) )
--EDIT PER THE ANSWER BELOW-- I added an IValueConverter Class:
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
}
And Changed the ModelView to use an Observable Collection ... Updated 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");
}
}
And updated 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>
I am not really sure if I will gain any performance improvement, given the IValueConverter creates a new PointCollection Each the ObservableCollection is changed.
THANK YOU everyone for helping me through this. I wanted to paste the complete code as working if any others run into the same thing.
EDIT #2 - After benchmarking the Observable w/ IValueConverter vs. Copying PointCollection to a new PointCollection, the Point Collection seems faster:
// 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");
}
called from
// 5000 point sinwave for testing
foreach (Point pt in sinWave) mvm.AddPoint2(pt);
x++;
If there is a better way, comments appreciated.
Try replacing the property, rather than clearing+re-populating it.
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");
}
It seems like bindings sometimes ignore the "PropertyChanged" event if the object reference is the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With