Reputation: 53
I have a graph that I want to change some ViewModel property so the whole graph would change accordingly.
The only property that I want to change here is "year", I tried to implement the INotifyPropertyChanged so the binding will cause the graph to change automatically, but it didn't work.
This is the model:
public class Model
{
public double rate { get; set; }
public string date { get; set; }
}
This is the ViewModel:
public class ViewModel :INotifyPropertyChanged
{
private string _year;
public string Year { get { return _year; } set { _year = value;UpdateData(); OnPropertyChanged("Year"); } }
public ViewModel()
{
_year = "2017";
UpdateData();
}
public void UpdateData()
{
int i,j;//Indexs that holds actuall api retrived values
string cR, urlContents;// cR- current rate in string format, urlContents - the whole Api retrived data
string c;//For api syntx, add 0 or not, depends on the current date syntax
this.CurrenciesHis = new ObservableCollection<Model>();//Model objects collection
HttpClient client = new HttpClient();
for (int l = 1; l < 13; l++)
{
if (l < 10)
c = "0";
else
c = "";
urlContents = client.GetStringAsync("http://data.fixer.io/api/"+(_year)+"-"+ c + l + "-01?access_key=&base=USD&symbols=EUR&format=1").Result;
i = urlContents.IndexOf("EUR");//Finds the desired value from api recived data
j = urlContents.IndexOf("}");
cR = urlContents.Substring(i + 5, (j - 2) - (i + 5));
CurrenciesHis.Add(new Model() { rate = Convert.ToDouble(cR), date = "01/" + l.ToString() });
}
}
public ObservableCollection<Model> CurrenciesHis { get; set; }
#region "INotifyPropertyChanged members"
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
This is the View that based on third party control (I deleted alot of XAML and used bold letters to mark where is the actuall binding located):
<layout:SampleLayoutWindow x:Class="AreaChart.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ResizeMode="CanResizeWithGrip"
xmlns:chart="clr-namespace:Syncfusion.UI.Xaml.Charts;assembly=Syncfusion.SfChart.WPF"
xmlns:local="clr-namespace:PL"
xmlns:layout="clr-namespace:Syncfusion.Windows.SampleLayout;assembly=Syncfusion.Chart.Wpf.SampleLayout"
UserOptionsVisibility="Collapsed"
WindowStartupLocation="CenterScreen" Height="643.287" Width="1250.5"
Title="2017">
<Grid>
<Grid.Resources>
...........................................
<chart:AreaSeries x:Name="AreaSeries" EnableAnimation="True"
**XBindingPath="date"
Label="Favourite"
YBindingPath="rate"
ItemsSource="{Binding CurrenciesHis}"**
ShowTooltip="True" >
<chart:AreaSeries.AdornmentsInfo>
<chart:ChartAdornmentInfo AdornmentsPosition="Bottom"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ShowLabel="True">
<chart:ChartAdornmentInfo.LabelTemplate>
<DataTemplate>
....................................
<TextBox HorizontalAlignment="Left" Height="30" Margin="28,231,0,0" TextWrapping="Wrap" Name="Text1" VerticalAlignment="Top" Width="76" Text="{Binding Year, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
This is the code behaind and the event of the Textbox that I want to change with it's help that year property of the viewmodel:
public partial class MainWindow : SampleLayoutWindow
{
PL.ViewModel newInstance;
public MainWindow()
{
InitializeComponent();
newInstance = new PL.ViewModel();
this.DataContext = newInstance;
}
}
What I understand is that from this point the mechanism of WPFwill change the values on the chart using the binding and the "notification" of INotifyPropertyChanged but it doesn't work for me..
Upvotes: 0
Views: 4779
Reputation: 37059
year
should be a private field, but it is public. You're setting the value of the field, which naturally does not execute any of the code in the setter for the property.
Make year
and all of your backing fields private, and rename all of your private fields with a leading underscore (for example, year
should be renamed to _year
) to prevent accidents like this.
And make it a policy in your viewmodel code always to set the property, never the field, except of course inside the actual property setter for that field.
Also, use bindings to set viewmodel properties from UI. Don't do it in codebehind. Get rid of that textchanged handler.
<TextBox
HorizontalAlignment="Left"
Height="30"
Margin="28,231,0,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="76"
Text="{Binding Year, UpdateSourceTrigger=PropertyChanged}"
/>
Finally, it seems that you intended for changes to Year
to have some effect on the contents of CurrenciesHis
, but there's no mechanism for that in your code, and no explanation of what you want to have happen or how you expect it to happen.
And here's an updated version of your viewmodel.
public class ViewModel
{
public ViewModel()
{
// DO NOT, DO NOT EVER, DO NOT, SERIOUSLY, EVER, EVER, EVER UPDATE A
// PROPERTY'S BACKING FIELD OUTSIDE THE PROPERTY'S SETTER.
Year = DateTime.Now.Year - 1;
UpdateCurrencies();
}
protected void UpdateCurrencies()
{
// Indexs that holds actuall api retrived values
int i, j;
// cR- current rate in string format, urlContents - the whole Api retrived data
string cR, urlContents;
// For api syntx, add 0 or not, depends on the current date syntax
string c;
CurrenciesHis = new ObservableCollection<Model>();//Model objects collection
HttpClient client = new HttpClient();
for (int l = 1; l < 13; l++)
{
if (l < 10)
c = "0";
else
c = "";
// Use the public property Year, not the field _year
var url = "http://data.fixer.io/api/" + Year + "-" + c + l + "-01?access_key=&base=USD&symbols=EUR&format=1";
urlContents = client.GetStringAsync(url).Result;
i = urlContents.IndexOf("EUR");//Finds the desired value from api recived data
j = urlContents.IndexOf("}");
cR = urlContents.Substring(i + 5, (j - 2) - (i + 5));
CurrenciesHis.Add(new Model() { rate = Convert.ToDouble(cR), date = "01/" + l.ToString() });
}
OnPropertyChanged(nameof(CurrenciesHis));
}
// Year is an integer, so make it an integer. The binding will work fine,
// and it'll prevent the user from typing "lol".
private int _year;
public int Year
{
get { return _year; }
set
{
if (_year != value)
{
_year = value;
OnPropertyChanged(nameof(Year));
UpdateCurrencies();
}
}
}
public ObservableCollection<Model> CurrenciesHis { get; private set; }
// -----------------------------------------------------
// YearsList property for ComboBox
// 30 years, starting 30 years ago.
// You could make this IEnumerable<int> or ReadOnlyCollection<int> if you
// want something other than the ComboBox to use it. The ComboBox doesn't care.
// Year MUST be an int for the binding to SelectedItem (see below) to work,
// not a string.
public System.Collections.IEnumerable YearsList
=> Enumerable.Range(DateTime.Now.Year - 30, 30).ToList().AsReadOnly();
}
XAML for YearsList combobox (which I prefer to the text box btw):
<ComboBox
ItemsSource="{Binding YearsList}"
SelectedItem="{Binding Year}"
/>
Upvotes: 1
Reputation: 4322
Your CurrenciesHis property doesn't implement INPC so WPF doesn't realize that you changed it (UpdateData() has "this.CurrenciesHis = new ObservableCollection();")
Your current property is:
public ObservableCollection<Model> CurrenciesHis { get; set; }
Should be something like this:
private ObservableCollection<Model> _CurrenciesHis;
public ObservableCollection<Model> CurrenciesHis { get { return _CurrenciesHis; } set { if (_CurrenciesHis != value) { _CurrenciesHis = value; OnPropertyChanged("CurrenciesHis"); } } }
Upvotes: 0