I've got 2 projects, one containing my model, the other one containing my view (Windows Form).
I tried to refresh my view, specifically a Label in accordance with model changes during my build() method using bindings but that didn't work. I don't know if my code is wrong or if it is impossible.
Edit : Actually, it seems that a label needs an Update() or Refresh() call to be updated graphically in his window... That can explain my problem
there is my Model class :
// ModelBuilder : INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private Substation currentSubstation;
public Substation CurrentSubstation
{
get
{
return this.currentSubstation;
}
set
{
if (value != this.currentSubstation)
{
this.currentSubstation = value;
NotifyPropertyChanged("CurrentSubstation");
}
}
}
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void Build()
{
foreach (Uri substationUri in substationsUri)
{
Substation substation = new Substation(substationUri); // long process
this.CurrentSubstation = substation;
}
}
There is my view
private void StartImportation_Click(object sender, EventArgs e)
{
this.model = new ModelBuilder();
// Old mistake:
//this.timeLabel.DataBindings.Add(new Binding("Text", this.model.CurrentSubstation,"name"));
this.timeLabel.DataBindings.Add(new Binding("Text", this.model, "CurrentSubstation.name"));
this.model.Build(); // I'd like to see the current substation created name
}
The reason is that you are binding to the initial this.model.CurrentSubstation but when you Build() the ModelBuilder a new Substation is assigned. However the old CurrentSubstation never changed during that process.
new Binding("Text", this.model.CurrentSubstation, "name")
Change the binding to
new Binding("Text", this.model, "CurrentSubstation.name")
Related
I have the visibility of a progress bar bound to The following property within my viewmodel:
public string CalcProgVisibility
{
get
{
return Calculation.CalcProgVisibility;
}
set
{
}
}
Calculation is my model, which can change the value. When the value changes within the model, what do I need to do to make sure the view is aware of this change?
EDIT:
Here is the property within my model too. I am using onpropertychanged but its not making it to the view.
I am changing the value within the model, the view is bound to my viewmodel and the viewmodel si trying to return a value taken from the model. I am updating the value on the model, and cannot push the fact that it has updated the value all the way down to the view, I can only get the viewmodel to see it has changed...
I updated the entire code. I hope it's clear now.
Define your control BindingMode = TwoWay
<TextBox Visibility="{Binding Path=CalcProgVisibility, Mode=TwoWay}"...
and call the OnPropertyChanged method on the setter of the property in your view model and also in your model
//Model
public class Calculation : INotifyPropertyChanged
{
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
//ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Calculation model)
{
this.CalcProgVisibility = model.CalcProgVisibility;
model.PropertyChanged += (s, e) => UpdateEntity(s as Calculation);
}
private void UpdateEntity(Calculation source)
{
CalcProgVisibility = source.CalcProgVisibility;
}
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Your Viewmodel has to implement the INotifyPropertyChanged Interface. To fire it in your case your viewmodel must also be aware of changes in your model object. So your model object could also implement INotifyPropertyChanged, or you use some form of the observer pattern.
If your model implements INotifyPropertyChanged, your viewmodel must manually register for this event and implement an handler. This could in turn trigger the PropertyChange event of the viewmodel then.
Another but in my opinion ugly way would be to scan (per timer or background thread) through your viemodel and check if a value changed since the last scan and then trigger a property changed event.
The first solution could look like this:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace StackOverflow
{
[TestClass]
public class IntegrationTest
{
[TestMethod]
public void NotifyPropertyChangeShouldFireOnViewModelWhenModelChanges()
{
//Arrange
Model model = new Model();
ViewModel sut = new ViewModel(model);
bool notifyPropertyChangeOnViewModelWasCalled = false;
sut.PropertyChanged += (sender, e) => { notifyPropertyChangeOnViewModelWasCalled = true; };
//Act
model.CalcValue = 4711;
//Assert
Assert.IsTrue(notifyPropertyChangeOnViewModelWasCalled, "NotifyPropertyChange was not fired on ViewModel");
}
}
public class ObjectWithNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Model : ObjectWithNotifyPropertyChanged
{
private double calcValue;
public double CalcValue
{
get
{
return calcValue;
}
set
{
if (calcValue != value)
{
calcValue = value;
RaisePropertyChanged();
}
}
}
}
public class ViewModel : ObjectWithNotifyPropertyChanged
{
public ViewModel(Model model)
{
this.model = model;
model.PropertyChanged += model_PropertyChanged;
}
void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CalcValue":
RaisePropertyChanged("CalcValue");
break;
}
}
private Model model;
public double CalcValue
{
get
{
return model.CalcValue;
}
}
}
}
I would like to display the name of the object currently built in a label (Windows Form)
My label doesn't refresh in real-time. When I put a DialogBox or something else after each iteration, the label takes the good value and is well updated, but when I let my code run without "breaks", the label seems to bug and his value never changes...
I have the same kind of code for a progress bar and everything is working well.
ModelBuilder.cs
public event PropertyChangedEventHandler PropertyChanged;
private Substation currentSubstation;
public Substation CurrentSubstation
{
get
{
return this.currentSubstation;
}
set
{
if (value != this.currentSubstation)
{
this.currentSubstation = value;
NotifyPropertyChanged();
}
}
}
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void Build()
{
foreach (string substationUri in substationsUri)
{
Substation substation = new Substation(new Uri(substationUri));
this.CurrentSubstation = substation;
/* code */
}
}
View.cs
private void StartImportation_Click(object sender, EventArgs e)
{
this.model = new ModelBuilder(....);
this.myLabel.DataBindings.Add(new Binding("Text", this.model, "CurrentSubstation.name"));
this.model.Build();
}
So I am trying to implement the MVVM pattern in a simple sample app. Essentially my app allows a user to choose from a list of search providers in a SettingsPage, and then in the MainPage when the user clicks the 'search' button he or she will be navigated to the search provider's website. Everything seems to work ok, no errors, except when navigating directly back to MainPage from SettingsPage the search property does not seem to be updated. Everything is fine though when the application is completely exited and launched fresh. What I have is as follows
MainPage.xaml.cs
void search_Click(object sender, EventArgs e)
{
TheBrowser.Navigate(App.ViewModel.SearchProvider.Address);
}
App.xaml.cs
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
MainViewMode.cs
public ListItem SearchProvider { get; private set; }
public MainViewModel()
{
SearchProvider = Settings.SearchProvider.Value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and in my SettingsPage is where I am allowin ga user to select a search provider
SettingsPage.xaml.cs
private void PopulateSearchProviderList()
{
searchProviderList = new ObservableCollection<ListItem>();
searchProviderList.Add(new ListItem { Name = "Bing", Address = "http://www.bing.com" });
searchProviderList.Add(new ListItem { Name = "Google", Address = "http://www.google.com" });
SearchProviderListPicker.ItemsSource = searchProviderList;
}
private void stk_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
if (SearchProviderListPicker.SelectedIndex != -1)
{
var selectedItem = (sender as StackPanel).DataContext as TestApp.Classes.ListItem;
Settings.SearchProvider.Value = selectedItem; //Setting the search provider
}
}
and finally my ListItem class which is fairly straightforward
ListItem.cs
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
So essentially I am not updating the ViewModel correctly based on the SettingsPage, but I am unsure of how to go about this properly.
You have to call the OnNotifyPropertyChanged("propertyName") for the item to update in the UI.
For example (assuming the Name and Address properties are bound to your UI elements.)
private string name;
private string address;
public string Name
{
get { return name;}
set {
name = value;
OnNotifyPropertyChanged("Name");
}
}
public string Address
{
get { return address; }
set {
address = value ;
OnNotifyPropertyChanged("Address");
}
}
There are a few issues I can see. We'll start from there.
Your MainViewModel needs to implement INotifyPropertyChanged see here
Your SearchProvider setter needs to raise PropertyChanged
You need to set the value of the SearchProvider. Currently that is only performed in the constructor which is probably why you are seeing things working on app startup only.
You need to make sure you are correctly binding the value of SearchProvider in your xaml. If you post your xaml we can check that out too.
In your ViewModel, add:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Update the SearchProvider property to something like:
private ListItem searchProvider;
public ListItem SearchProvider
{
get { return searchProvider; }
set
{
searchProvider = value;
OnPropertyChanged();
}
}
I have a page that already has a DataContext.
When i change the pivot item, I need to bind another list to another collection.
How to achieve this?
Here is the first DataContext that shows first pivotitem info.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigationContext.QueryString.TryGetValue("id", out _embarqueId))
{
String json = JsonConvert.SerializeObject(_embarqueId);
using (IntrepService service = new IntrepService())
{
String retornojson = service.ObterDetalhesEmbarque(json);
EmbarqueAtual = JsonConvert.DeserializeObject<EmbarqueViewModel>(retornojson);
DataContext = EmbarqueAtual;
}
VerificaConclusao();
}
}
Then I try to load the second collection to the listbox, but doesn't work:
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_itemsareloaded && ((PivotItem)pivot.SelectedItem).Header.Equals("itens"))
{
using (IntrepService service = new IntrepService())
{
String json = JsonConvert.SerializeObject(_embarqueId);
var retorno = service.ObterItensEmbarque(json);
ItensDoEmbarque = JsonConvert.DeserializeObject<ObservableCollection<ItemDeEmbarqueViewModel>>(retorno);
lstItens.DataContext = ItensDoEmbarque;
}
}
}
You should have one ViewModel to hold all of your data that you want to bind to. Set this ViewModel as your datacontext.
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ItemDeEmbarqueViewModel> _itensDoEmbarque;
private EmbarqueViewModel _embarqueAtual;
public ViewModel()
{
ItensDoEmbarque = new ObservableCollection<ItemDeEmbarqueViewModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ItemDeEmbarqueViewModel> ItensDoEmbarque
{
get { return _itensDoEmbarque; }
set
{
_itensDoEmbarque= value;
OnPropertyChanged("ItensDoEmbarque");
}
}
public EmbarqueViewModel EmbarqueAtual
{
get { return _embarqueAtual; }
set
{
_embarqueAtual = value;
OnPropertyChanged("EmbarqueAtual");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Within your OnNavigatedTo method set both of the properties and set the DataContext to be this object. You xaml would need to change to bind to the properties of these items instead of {Binding}
You can set the collections that the PivotItem will be bound to ahead of time without worry of rendering delay. PivotItems delay rendering until they are shown.
So i have something along the lines of
private ObservableCollection<ViewModel> _internal;
public ObservableCollection<ViewModel> BoundInternal{get;set}; //this is Binded in the Itemssource like ItemSource={Binding BoundInternal}
Now In my code i do something like
BoundInternal=_internal, However the problem is the BoundInternal isn't trigger any collectionChanged event. I have to use the Add method. So I am wondering if there is a solution to this.
Here is what I suspect your code ought to look like like (although its not quite a match for what you currently doing):-
public class YourClassHoldingThisStuff : INotifyProperyChanged
{
private ObservableCollection<ViewModel> _internal;
public ObservableCollection<ViewModel> BoundInternal
{
get { return _internal; }
set
{
_internal = value;
NotifyPropertyChanged("BoundInternal");
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new ProperytChangedEventArgs(name));
}
}
In this case the _internal field becomes the source of the value of BoundInternal directly and you should only assign it via BoundInternal, (don't assign a value directly to _internal). When that occurs anything currently bound to it will be informed of the change.
If for some reason you really do need to maintain _internal as a separate reference from the backing field of BoundInternal then:-
public class YourClassHoldingThisStuff : INotifyProperyChanged
{
private ObservableCollection<ViewModel> _internal;
private ObservableCollection<ViewModel> _boundInternal;
public ObservableCollection<ViewModel> BoundInternal
{
get { return _boundInternal; }
set
{
_boundInternal = value;
NotifyPropertyChanged("BoundInternal");
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new ProperytChangedEventArgs(name));
}
}
Now at some point in your code when you do BoundInternal = _internal, anything bound to it will be informed of the change.
Every ItemsControl has a, Items property which has a Refresh() method that you can call, which will update your list.
MyList.Items.Refresh()