如何使用Mvvm在运行时更改datacontext

时间:2018-04-24 18:54:43

标签: wpf

我有一个图表,我想要更改一些ViewModel属性,以便整个图形会相应更改。

我想在这里更改的唯一属性是" year",我尝试实现INotifyPropertyChanged,因此绑定会导致图表自动更改,但它无法正常工作。

这是模型:

 public class Model
{

    public double rate { get; set; }

    public string date { get; set; }

}

这是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

这是基于第三方控件的视图(我删除了很多XAML并使用粗体字母来标记实际绑定所在的位置):

<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>

这是代码行为和我想要更改的文本框事件,它帮助viewmodel的年属性:

 public partial class MainWindow : SampleLayoutWindow
{

    PL.ViewModel newInstance;

    public MainWindow()
    {
        InitializeComponent();

        newInstance = new PL.ViewModel();
        this.DataContext = newInstance;
    }



  }

据我所知,从这一点来看,WPF的机制将使用绑定和&#34;通知&#34;来更改图表上的值。 INotifyPropertyChanged,但它对我不起作用..

2 个答案:

答案 0 :(得分:1)

year应该是私有字段,但它是公开的。您正在设置字段的值,该字段自然不会执行属性的setter中的任何代码。

year和所有支持字段设为私有,并使用前导下划线重命名所有私有字段(例如,year应重命名为_year)以防止发生意外像这样。

并在viewmodel代码中设置一个策略,始终设置属性,而不是字段,当然除了该字段的实际属性设置器之外。

此外,使用绑定从UI设置viewmodel属性。不要在代码隐藏中这样做。摆脱那个textchanged处理程序。

<TextBox 
    HorizontalAlignment="Left" 
    Height="30" 
    Margin="28,231,0,0" 
    TextWrapping="Wrap" 
    VerticalAlignment="Top" 
    Width="76" 
    Text="{Binding Year, UpdateSourceTrigger=PropertyChanged}"
    />

最后,您似乎打算对Year的更改对CurrenciesHis的内容产生一些影响,但在您的代码中没有相应的机制,也没有解释您想要的内容已经发生或者你期望它如何发生。

这是您的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组合框(我更喜欢文本框btw):

<ComboBox
    ItemsSource="{Binding YearsList}"
    SelectedItem="{Binding Year}"
    />

答案 1 :(得分:0)

你的CurrenciesHis属性并没有实现INPC,所以WPF没有意识到你改变了它(UpdateData()有&#34; this.CurrenciesHis = new ObservableCollection();&#34;)

您当前的财产是:

public ObservableCollection<Model> CurrenciesHis { get; set; }

应该是这样的:

private ObservableCollection<Model> _CurrenciesHis;
public ObservableCollection<Model> CurrenciesHis { get { return _CurrenciesHis; } set { if (_CurrenciesHis != value) { _CurrenciesHis = value; OnPropertyChanged("CurrenciesHis"); } } }