I'm new to WPF, and I'm encountering a problem. I have a ListBox with images arranged in a floorplan, the image source is bound to a uri in the code. uri is to an embedded resource in the project. This all works fine on startup, until I have to change an image. No exceptions, but the images do not change. When I run in debug, I can see that even though the ObservableCollection items change, the mainwindow images do not.
Is this due to caching, or is it something else? I try to disable caching in the xaml, but I get the error The TypeConverter for "CacheMode" does not support converting from a string.
I'll do my best to be brief with the code and only include relevant information:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ObservableCollection<ComputerInfo> CompListObs;
public ObservableCollection<ComputerInfo> compListObsNotifier
{
get { return CompListObs; }
set
{
CompListObs = value;
RaisePropertyChanged("compListObsNotifier");
}
}
//...
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
// This correctly loads the images from the xml and displays them on the main window:
var items = XDocument.Load(#"path to xml")
.Descendants("Computer")
.Select(i => new ComputerInfo {
MachineName = (string)i.Element("MachineName"),
Lat = (double)i.Element("Long"),
Lon = (double)i.Element("Lat"),
CurrentImage = ResourceHelper.LoadBitmapURIFromResource((string)i.Element("Img"))
}).ToList();
CompListObs = new ObservableCollection<ComputerInfo>(items);
}
public void MainTimer_Tick(object sender, EventArgs e)
{
// This runs fine and I can see the members of CompListObs are changing,
// but the images on the mainwindow are not changing.
foreach(var comp in CompListObs) { comp.CurrentImage = ResourceHelper.LoadBitmapURIFromResource("another image");
}
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public class ComputerInfo : INotifyPropertyChanged
{
public ClientInfo ClientsInfo { get; set; }
public string MachineName { get; set; }
public Uri CurrentImage { set; get; }
public double Lat { get; set; }
public double Lon { get; set; }
public Uri currentImageNotifier
{
get { return CurrentImage; }
set
{
CurrentImage = value;
RaisePropertyChanged("compListObsNotifier");
}
}
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Here is the xaml:
<ListBox ItemsSource="{Binding compListObsNotifier}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Path=CurrentImage}" Height="65"></Image>
</DataTemplate>
</ListBox.ItemTemplate>
<!-- after this, I place <ListBox.ItemsPanel> <ItemsPanelTemplate> and <Canvas> in the ListBox.
When you bind to an observable collection you are subscribing to changes in the collection (for instance, adding or removing items from the collection). This does not subscribe you to changes to properties on the individual items in the collection. As others have stated, if you want to bind to a property on the items, you will need to raise a PropertyChanged event when the property changes.
private Uri currentImage;
public Uri CurrentImage
{
get { return currentImage; }
set
{
currentImage = value;
RaisePropertyChanged("CurrentImage");
}
}
Note: You may want to wrap the setter in an if statement and use Uri.Compare() to determine if the given value is actually different from the current value. This way, you only raise a PropertyChanged event when the property actually changes.
Also, in your code example you are setting the CurrentImage property in this foreach loop:
foreach(var comp in CompListObs)
{
comp.CurrentImage = ResourceHelper.LoadBitmapURIFromResource("another image");
}
However, you're raising your PropertyChanged event from the currentImageNotifier property. It's okay to raise PropertyChanged events from a location outside of the property being modified, but you either need to replace the CurrentImage assignment in your foreach loop with currentImageNotifier, or modify your CurrentImage property to raise its own PropertyChanged event. As it stands, you're not actually raising a PropertyChanged event on the property you're binding to.
Honestly, it doesn't look like you even need the currentImageNotifier property. It's not doing anything you couldn't just do with the CurrentImage property directly.
When you are binding to CurrentImage and the value of CurrentImage changes, you must raise the property changed event for CurrentImage.
Based on the supplied code, you also could do this:
public Uri currentImageNotifier
{
get { return CurrentImage; }
set
{
CurrentImage = value;
RaisePropertyChanged("CurrentImage");
}
}
Related
I have a gridview shown as below in XAML
<ListView x:Name="listTasks">
<ListView.View>
<GridView x:Name="gridTasks">
<GridViewColumn Header="ID" HeaderStringFormat="Lowercase" Width ="26" DisplayMemberBinding="{Binding id}"/>
<GridViewColumn Header="Something" Width="113" DisplayMemberBinding="{Binding something}"/>
<GridViewColumn Header="State" Width="179" DisplayMemberBinding="{Binding currentState}"/>
</GridView>
</ListView.View>
</ListView>
and i have a button which adds to this gridview using the below
m.myList.Add(new mylistview.myitems
{
id = m.id,
something= m.something,
currentState = m.currentState,
});
This button works perfectly by adding the row into the gridview. However I would like to modify theCurrentState using a method that is running. How would I locate for example, ID = "8" and then modify theCurrentState for that row?
UPDATED CODE SHOWN
I've now replaced my list<Task> with ObservableCollection and managed to get it to add to my listview when I click onto my button. However, I am struggling to implement the iNotifyPropertyChanged into my code and getting it to work correctly... Below is my listview class
public class mylistview : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _currentState;
public string currentState
{
get { return _currentState; }
set
{
_currentState = value;
OnPropertyChanged();
}
}
public ObservableCollection<myitems> _myList = new ObservableCollection<myitems>();
public ObservableCollection<myitems> myList
{
get { return _myList; }
}
private static int _id = 0;
public class myitems
{
public int id { get; set; }
public string something{ get; set; }
public string currentState { get; set; }
}
public int id
{
get { return _id; }
set { _id = value; }
}
}
So I see you're using data bindings already, that's good. But your question makes me think you haven't quite grasped everything it can do for you yet.
My recommendation would be to forget about adding items directly to listOfTasks.Items. Instead you should make an ObservableCollection to hold that list and bind the listOfTasks to it. Like so:
ObservableCollection tasks = new ObservableCollection<mylistview.myitems>();
ListOfTasks.ItemsSource = tasks;
With that binding in place you should be able to simply add new items to the tasks list when they click your button:
tasks.Add(new mylistview.myitems
{
id = theId,
something= something,
currentState = theCurrentState,
});
and it should automatically update the GUI.
The last step is to make sure that the class mylistview.myitems implements INotifyPropertyChanged. This is easier than it sounds; you just need to have it trigger an event any time the property is set. Something like so:
public class exampleProperties: INotifyPropertyChanged
{
//this is the event you have to emit
public event PropertyChangedEventHandler PropertyChanged;
//This is a convenience function to trigger the event.
//The CallerMemberName part will automatically figure out
//the name of the property you called from if propertyName == ""
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
//Any time this property is set it will trigger the event
private string _currentState = "";
public string currentState
{
get { return _currentState; }
set
{
if (_currentState != value)
{
_currentState = value;
OnPropertyChanged();
}
}
}
}
Now that the gridview is bound to an ObservableCollection and the items held in that collection can notify interested GUI controls that their properties have changed, you should simply be able to update the GUI simply by changing the appropriate item in the collection.
And here's an example of a form that uses the whole technique: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).asp
edit
I forgot that you specifically need to bind to the ItemSource property of the ListView. The way I have done it in the past is to set ItemsSource={binding} in the ListView's xaml and then assign an ObservableCollection to ListView.DataContext. However I have found an easier way and updated the original post with it. Here's a reference: http://www.wpf-tutorial.com/listview-control/listview-with-gridview/
Edit 2
Aha, you're adding the iPropertyChangedNotify to the wrong thing. It goes on the myitems class like so:
public class myitems : iNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string something{ get; set; }
public string currentState { get; set; }
}
I leave updating the current state and something properties as an excersize. They also need to trigger the OnPropertyChanged event when their value is set.
Maybe with
listOfTasks.Items.Cast<ListViewItem>().First(item => item.ID == "8").theCurrentState = newState;
//I'm not sure about the Cast stuff, because I don't know what types the ListView uses for its items
Of course you could iterate through the items with a loop and check manually for the ID as well.
I'm trying to fiddle a little with WPF bindings, so I created a simple project.
Here's the code:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age {
get { return age; }
set {
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if(PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
My viewmodel contains ObservableCollection of Person, and single Person to track selected Person.
I've bound listbox's ItemsSource to ObservableCollection, and SelectedItem to single Person, called CurrentPerson. Also, I've bound TextBox to CurrentPerson.Name.
Code works fine, but whenever I change content of TextBox - my listbox also changes. And no matter what combination of "OneWay, TwoWay, OneWayToSource" binding modes on listbox\selecteditem I cannot prevent listbox from updating from CurrentPerson.
How can I prevent this behavior? I'd like to update listbox from CurrentPerson only by using ICommand interface from VM.
There is only one copy of the Person object which is being used in both ListBox.ItemsSource and TextBox.Text, so naturally updating that object from one location will reflect the change in the other as well.
Two easy solutions would be
Change the BindingMode on TextBox.Text to Explicit, so it doesn't update the Person object until you tell it to
Use a separate string property for TextBox.Text and copy it over to your SelectedPerson.Name whenever the command executes
Personally I prefer the second option because I'm not a big fan of bindings that don't accurately reflect the data object behind the UI component, and it would allow the user to change the SelectedItem without resetting the TextBox value.
For an example of the second option, your ViewModel might look like this :
public class MyViewModel()
{
ObservableCollection<Person> People { get; set; }
Person SelectedPerson { get; set; }
string NewPersonName { get; set; }
ICommand UpdatePersonName { get; }
}
where the UpdatePersonName command would execute
SelectedPerson.Name = NewPersonName;
and the CanExecute would only return true if
SelectedPerson != null
&& !NewPersonName.IsNullOrWhiteSpace()
&& NewPersonName != SelectedPerson.Name
I'm not sure if I've followed the question properly.
So, we have a class Person as
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age
{
get { return age; }
set
{
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
And we have a view model as
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Person> List { get; set; }
Person currentPerson;
public Person CurrentPerson {
get { return currentPerson; }
set { currentPerson = value;
FirePropertyChanged("CurrentPerson");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The xaml is
<ListBox ItemsSource="{Binding List}" SelectedItem="{Binding CurrentPerson}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" Width="100" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And I bind the view model to the view via
ViewModel vm = new ViewModel();
vm.List = new ObservableCollection<Person>();
foreach (var i in Enumerable.Range(1,10))
{
vm.List.Add(new Person() { Name = "Test" + i.ToString(), Age= i });
}
vm.CurrentPerson = null;
this.DataContext = vm;
Whenever I change the value at textbox, it updates the name properly. I tried to add a handler for list changed, but it doesn't happen to get triggered.
vm.List.CollectionChanged += List_CollectionChanged;
void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
MessageBox.Show(e.Action.ToString());
}
Can you comment if it isn't the same as your problem statement?
If you want to control when and what is saved/updated, you obviously need is a ViewModel for editing your Person model.
When selecting a person in your Listbox, you have to pass the person's id (avoid passing the object itself) to the PersonEditViewModel which is bound to the properties that shall be edited, load the persons data into the PersonEditViewModel and then edit. Once you hit the "Save" button, it should commit the change and update the database or whatever you are using for persistence.
Use either events/messages to pass values/events back and forth, or use a navigation approach (like INavigationAware interface in Prism).
I don't understand why when I update a object, my bound controls do not update.
The data displays fine initially, but when I want to refresh the data displayed in the UI nothing happens when I update the object. The object updates fine. The ViewModel does use INotifyPropertyChanged on all fields.
However if I update individual items directly, I can update my UI. As commented below.
I guess I've made a school boy error somewhere here?
UPDATE: I've added the model to the question. While I understand the answers, I don't understand how to implement it. Attempted to implement a collection changed event without success. Can I have some pointers please?
public partial class CisArrivalsPanel : UserControl
{
private ApiDataArrivalsDepartures _theArrivalsDepartures;
public CisArrivalsPanel()
{
InitializeComponent();
_theArrivalsDepartures = new ApiDataArrivalsDepartures();
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Kings Cross");
this.DataContext = _theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
//However this (when uncommented, and I comment out the above line) does update the UI**
//_theArrivalsDepartures.StationMovementList[0].OriginName = "test";
//_theArrivalsDepartures.StationMovementList[0].Platform = "0";
//_theArrivalsDepartures.StationMovementList[0].BestArrivalEstimateMins = "999";
//_theArrivalsDepartures.StationName = "test";
}
private void StationHeader_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Reload();
Debug.WriteLine(_theArrivalsDepartures.StationName);
foreach (var a in _theArrivalsDepartures.StationMovementList)
{
Debug.WriteLine(a.OriginName);
Debug.WriteLine(a.BestArrivalEstimateMins);
}
}
}
EDIT : Added Model
public class ApiDataArrivalsDepartures : INotifyPropertyChanged
{
private string _stationName;
[JsonProperty(PropertyName = "station_name")]
public string StationName {
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private List<StationListOfMovements> _stationMovementList;
public List<StationListOfMovements> StationMovementList
{
get
{
return _stationMovementList;
}
set
{
_stationMovementList = value;
NotifyPropertyChanged("StationMovementList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class StationListOfMovements : INotifyPropertyChanged
{
private string _originName;
[JsonProperty(PropertyName = "origin_name")]
public string OriginName {
get
{
return _originName;
}
set
{
_originName = value;
NotifyPropertyChanged("OriginName");
}
}
[JsonProperty(PropertyName = "destination_name")]
public string DestinationName { get; set; }
private string _platform;
[JsonProperty(PropertyName = "Platform")]
public string Platform {
get
{
return _platform;
}
set
{
_platform = value;
NotifyPropertyChanged("Platform");
}
}
private string _bestArrivalEstimateMins;
[JsonProperty(PropertyName = "best_arrival_estimate_mins")]
public string BestArrivalEstimateMins {
get
{
return _bestArrivalEstimateMins;
}
set
{
_bestArrivalEstimateMins = value;
NotifyPropertyChanged("BestArrivalEstimateMins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
There are two pieces here pertaining to your collection (technically three):
If you want a new collection to propagate, the collection property has to raise PropertyChanged (sounds like it does)
If you want add/remove on the collection to propagate, you need to use a collection that implements INotifyCollectionChanged. ObservableCollection is a good choice.
If you want changes to the items in the container to propagate, then those items need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Make sure all those are covered, and the changes should appear on the UI as you expect.
You should update the DataContext and ItemsSource too.
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
DataContext = theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
Use for the collection ObservableCollection , this class notify the ui when change to the collection occurred
your reload function works because the there is PropertyChanged on all the fields include this one
it notify the ui and reload the correct collection
I am trying to set up a multi-language application, so when the user changes the display language all the texts in all the open windows change automatically.
I am having issues through with binding combo-box control. The binding needs to be done in code-behind as I have dynamic content coming from a database, and sometimes I even have to create additional combo-boxes at runtime.
Also I do not want to keep the translations in the database because I do not want to query the database every time a user is changing the display language.
What I did until now:
in xaml:
<ComboBox x:Name="cmb"/>
and in C#:
public class MyCmbItem
{
public int Index { get; set; }
public string Text { get; set; }
}
private ObservableCollection<MyCmbItem> LoadText()
{
ObservableCollection<MyCmbItem> _result = new ObservableCollection<MyCmbItem>();
foreach (var _item in _list)
{
//the list is coming from a database read
_result.Add(new MyCmbItem { Index = _item.Value, Text = _res_man_global.GetString(_item.KeyText, _culture) });
}
return _result;
}
public ObservableCollection<MyCmbItem> MyTexts
{
get { return LoadText(); }
set {} //I do not have to add/remove items at runtime so for now I leave this empty
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
...
LoadList(); //this adds values in _list
cmb.ItemsSource = MyTexts; //this populates the combo-box
Here I got stuck and I do not know how to determine the combo-box to refresh the displayed texts. The method must achieve that if I have several windows opened each containing a random number of combo-boxes, when I change the current language all the combo-boxes in all the windows will refresh the displayed list, without affecting other values inside (like the selected item). Does anybody know how this can be done?
Many thanks.
For your xaml UI, the INotifyPropertyChanged interface indicates updates of the viewmodel. You can extend your class like this:
public class MyCmbItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string APropertyName)
{
var property_changed = PropertyChanged;
if (property_changed != null)
{
property_changed(this, new PropertyChangedEventArgs(APropertyName));
}
}
private string _Text;
private string _KeyText;
public int Index { get; set; }
public string Text
{
get { return _Text;}
set {
if (_Text != value)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
public MyCmbItem(string key_text, int index)
{
Index = index;
_KeyText = key_text;
RefreshText();
_res_man_global.LanguageChanged += () => RefreshText();
}
public void RefreshText()
{
Text = _res_man_global.GetString(_KeyText, _culture);
}
}
Your view can simply bind to the Text-property as following:
<DataTemplate DataType="{x:Type local:MyCmbItem}">
<TextBlock Text="{Binding Path=Text}"/>
</DataTemplate>
Note: I assumed that your language class is global and has some kind of language-changed notification event.
Below is an example of my model , ViewModel and xaml binding. The viewmodel implements INotifyPropertChanged. The problem i'm having is...when the wpf form first loads i set ActiveStock and i see both setter and getter being called and the ui is updated to reflect the data correctly.
However, when i later set StockViewModel.ActiveStock, FirePropertyChanged is invoked but i don't see the getter being called, and consequently the UI does not update to reflect the new data. Any ideas what might be happening here?
The second question i have is whether i also need to raise PropertyChanged for the child properties (PriceData and CompanyData) of my model when ViewModel.ActiveStock is changed?
public class Stock
{
public string Ticker { get; set; }
public StockData PriceData { get; set; }
public StockData CompanyData { get; set; }
}
public class StockData
{
...
}
public class StockViewModel:INotifyPropertyChanged
{
private Stock _activeStock;
public Stock ActiveStock
{
get{ return _activeStock;}
set{ _activeStock = value; FirePropertyChanged("ActiveStock");}
}
...
}
XAML:
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding PriceData}" />
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding CompanyData}" />
Edit:
if i remove the DataContext binding for the UserControl and instead set the DataContext for these two controls in code behind when ActiveStock changes, it works fine. why???
The getter is not being called because as far as I can see nothing is "getting" the value, The only properties used are PriceData and CompanyData and these don't use INotifyPropertyChanged
You will have to implement INotifyPropertyChanged on your Stock class for the UI to reflect the changes.
public class Stock : INotifyPropertyChanged
{
private string _ticker;
private StockData _priceData;
private StockData _companyData;
public string Ticker
{
get { return _ticker; }
set { _ticker = value; NotifyPropertyChanged("Ticker"); }
}
public StockData PriceData
{
get { return _priceData; }
set { _priceData = value; NotifyPropertyChanged("PriceData"); }
}
public StockData CompanyData
{
get { return _companyData; }
set { _companyData = value; NotifyPropertyChanged("CompanyData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You might want to try to specify the mode property on your datacontext bindings.
DataContext="{Binding ActiveStock, Mode=OneWay}"
I'm not sure that OneTime is the default binding for DataContext, but it would explain so if the above helps.
The second question has been answered by sa_ddam213.
HTH