I'm trying to serialize and deserialize this ObservableCollection:
public class DataCollection : ObservableCollection<Data>
{
}
public class Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool? _enabled;
public string Name { get; set; }
public bool? Enabled
{
get { return _enabled; }
set { _enabled = value; NotifyPropertyChanged("Enabled"); }
}
public Data(string name, bool? enabled)
{
this.ScriptName = name;
this.Enabled = enabled;
}
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Using this class (as in the What is the easiest way to save an Observable collectin of object to an XML file? example):
class UserPreferences
{
private DataCollection _dataLst;
private const string _dataLstFileName = "Data.xml";
public DataCollection DataLst { get { return _dataLst; } set { _dataLst = value; } }
public UserPreferences()
{
Load();
}
public void Load()
{
if (File.Exists(_dataLstFileName))
{
using (var reader = new StreamReader(_dataLstFileName))
{
var xs = new XmlSerializer(typeof(DataCollection));
_dataLst = (DataCollection) xs.Deserialize(reader);
}
}
else
{
_dataLst = new DataCollection();
}
}
public void Save()
{
using (var writer = new StreamWriter(_dataLstFileName))
{
var xs = new XmlSerializer(typeof(DataCollection));
xs.Serialize(writer, _dataLst);
}
}
}
And here is how I call it from my app:
public partial class MainWindow : Window
{
private DataCollection DataLst;
...
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
var userPrefs = new UserPreferences();
userPrefs.DataLst = DataLst; // DataList isn't null and contains correct data
userPrefs.Save();
}
}
But it creates empty file and hangs up (even without exceptions, just app window becomes black and doesn't response) in the line
var xs = new XmlSerializer(typeof(DataCollection));
After a little research it seems that there are problems when serializing ObservableCollection<T>. See this blog post for more information.
In summary:
The problem is that the events have not been marked as non-serialized. Therefore, whenever you try to serialize an instance of ObservableCollection, you will also be attempting to serialize any event handlers. When you're using the collection for its primary scenario (data binding), you will have WPF controls attached to the events.
(Kent Boogaart)
Your Data class will also suffer from similar problems; update your PropertyChanged event like so:
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
As well as the updates already mentioned by other people, your Data class should also be marked as Serializable.
Your Data class must have a parameter less constructor, otherwise XmlSerializer will never be able to create instance of Data.
Instead of storing DataCoollection, you should store and retrieve Data[], it's easier without having to do anything else.
While storing, you can call ToArray method to get Data[] and use typeof(Data[]) for serializer.
While reading you can read the array and add items into your collection.
Related
I have a custom form where I have a GridView on it. Most of my forms will inherit from this custom form.
so let's say that I have
class A : B
{
//Contents
}
with the above scenario, my problem is: I am not able to edit the grid's columns,
on the designer view's property grid. it's like they are locked.
so I have decided to create a custom property to set a list of column names etc.
so to do this I have these classes
[TypeConverter(typeof(BrowseLayoutColumns))]
public class BrowseLayoutColumns : ExpandableObjectConverter
{
#region Properties
private string _columnName = string.Empty;
public string ColumnName
{
get => _columnName;
set
{
if (null == value) return;
_columnName = value;
}
}
private string _bindingField = string.Empty;
public string BindingField
{
get => _bindingField;
set
{
if (null == value) return;
_bindingField = value;
}
}
#endregion
public override string ToString()
{
return "Columns";
}
}
internal class MyList<T> : List<T> where T : class
{
#region ListMethods
public new void Add(T item)
{
base.Add(item);
ListChanged?.Invoke();
}
public new void Clear()
{
base.Clear();
ListChanged?.Invoke();
}
#endregion
#region Events
public event ListChangedEventHandler ListChanged;
public delegate void ListChangedEventHandler();
#endregion
}
and inside my Custom class I added
private MyList<BrowseLayoutColumns> _browseLayoutColumns = new MyList<BrowseLayoutColumns>();
[Category("Design")]
public MyList<BrowseLayoutColumns> BrowseLayoutColumns
{
get => _browseLayoutColumns;
set => _browseLayoutColumns = value;
}
and inside form Initialization I've created the ListChanged event.
private void _browseLayoutColumns_ListChanged()
{
if (_browseLayoutColumns == null) return;
foreach (var column in _browseLayoutColumns)
{
myGridView1.Columns.Add(column.ColumnName, column.BindingField);
}
}
so now as you can see below in the design time I'm able to add columns
the problem here is, it's like the data entered here is not persistent, I mean, it is not adding these values to the columns because my event is not triggered when I run the program and when I debug I see that my BrowseLayoutList property is empty.
any help?
P.S I've tested my event and others by adding to browselayoutcolumns property manually
I'm completely new to WPF and I'm having problems with ItemsSource updates.
I created a single main window Metro application with tabs (TabItem(s) as UserControl DataContext="{Binding}") in which different data is displayed / different methods used.
What I've found myself struggling with is INotifyPropertyChanged (I wasn't able to understand the solution of my problem from similar examples/questions) interface's concept. I'm trying to make that if new data is entered in a window (which is initialized from one of the UserControl), a ComboBoxin another UserControl (or TabItem) would be automatically updated. Here's what I have:
UserControl1.xaml
public partial class UserControl1: UserControl
{
private userlist addlist;
public UserControl1()
{
InitializeComponent();
fillcombo();
}
public void fillcombo()
{
Fillfromdb F = new Fillfromdb(); // class that simply connects
// to a database sets a datatable as ListCollectionView
addlist = new addlist { List = F.returnlistview() }; // returns ListCollectionView
UsersCombo.ItemsSource = addlist.List;
}
userlist.cs
public class userlist: INotifyPropertyChanged
{
private ListCollectionView _list;
public ListCollectionView List
{
get { return this._list; }
set
{
if (this._list!= value)
{
this._list= value;
this.NotifyPropertyChanged("List");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Registration.xaml (called from another UserControl)
public partial class Registration: MetroWindow
{
public Registration()
{
InitializeComponent();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
Here's the ComboBox's setting in UserControl.xaml:
<ComboBox x:Name="UsersCombo"
ItemsSource="{Binding List, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Since I don't have any programming education/experience a very generic advice/explanation would be very much appreciated.
EDIT: Registration.xaml with propertychanged (still doesn't work):
public partial class Registration : MetroWindow
{
public userlist instance = new userlist();
public ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is save to database
// still don't now what to do with new ListCollectionView from database
}
public void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List; // when new data from database should be loaded?
}
This is where PropertyChanged event comes handy.
Bind the combobox in second xaml page to a List and create a similar property like in first xaml.
In second xaml.cs
public partial class Registration: MetroWindow, INotifyPropertyChanged
{
private userlist instance = new userlist();
private ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List;
//or iterate through the list and add as below
foreach(var item in instance.List)
{
List1.Add(item);
}
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
I'm newbie in MVVM design pattern, and I have these viewmodels :
ClassAViewModel
public class ClassAViewModel : INotifyPropertyChanged
{
private int _nbre = 0;
public int Nbre
{
get
{
return _nbre;
}
set
{
_nbre = value;
PropertyChanged(this, new PropertyChangedEventArgs("Nbre"));
}
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
And ClassBViewModel
PUBLIC class ClassBViewModel: INotifyPropertyChanged
{
private Boolean _IsBiggerthanFive = false;
public bool IsBiggerthanFive
{
get
{
return _IsBiggerthanFive;
}
set
{
_IsBiggerthanFive = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsBiggerthanFive"));
}
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I need to know if a mecanism of notification between two viewmodels exists , ie in my case if _nbre > 5 in the first viewmodel, the second viewmodel will be notified and the value of _IsBiggerthanFive will be changed. So:
How can two viewmodels communicate between them without instanciate one in the other ?
What is the best way to accomplish this task?
I agree with other commenters that the mediator/pub-sub/event aggregator/messenger is a good way to go. If you're not using an MVVM framework with a built-in solution, then I recommend this simple approach that takes advantage of the Reactive extensions:
public class EventPublisher : IEventPublisher
{
private readonly ConcurrentDictionary<Type, object> subjects
= new ConcurrentDictionary<Type, object>();
public IObservable<TEvent> GetEvent<TEvent>()
{
var subject =
(ISubject<TEvent>) subjects.GetOrAdd(typeof (TEvent),
t => new Subject<TEvent>());
return subject.AsObservable();
}
public void Publish<TEvent>(TEvent sampleEvent)
{
object subject;
if (subjects.TryGetValue(typeof(TEvent), out subject))
{
((ISubject<TEvent>)subject)
.OnNext(sampleEvent);
}
}
}
That's your whole event aggregator. Pass an instance of it into each view model, and store it as a reference. Then create a class to store your event details, let's say "ValueChangedEvent":
public class ValueChangedEvent
{
public int Value
{
get { return _value; }
}
private readonly int _value;
public ValueChangedEvent(int value)
{
_value = value;
}
}
Publish like this from the first view model:
set
{
_nbre = value;
PropertyChanged(this, new PropertyChangedEventArgs("Nbre"));
_eventPublisher.Publish(new ValueChangedEvent(value));
}
Subscribe in the other class using GetEvent:
public class ClassBViewModel: INotifyPropertyChanged, IDisposable
{
private readonly IDisposable _subscriber;
public ClassBViewModel(IEventPublisher eventPublisher)
{
_subscriber = eventPublisher.Subscribe<ValueChangedEvent>(next =>
{
IsBiggerthanFive = next.Value > 5;
});
}
public void Dispose()
{
_subscriber.Dispose();
}
}
A messenger service is a solution. MVVM Light Toolkit has an implementation of this. What you can do with it, is listen to a specific type of message in your viewmodel and handle it through the messenger. http://www.mvvmlight.net/
Maybe I don't understand the ObservableCollection well enough. But as far as I knew it was similar to a normal list, but with event triggers so that you can react to changes.
So I have this Windows store app. And in this application I have a main BusinessModel class which is the main source for all data in my client application. This data will be updated when the server has made some changes elsewhere. In the future I'd like to have this class update the ViewModels for specific data updates etc.
So I also have a ViewModel class which contains, at least in my PoC's so far, a copy of that list (also in the near future this list will have an enriched version of the list).
Since it's a copy they should be both separate instances and have their own separate items.
However when I update the copy in the ViewModel, the BusinessModel version changes with it.
And vice versa.
I can't seem to figure out why this is happening. Underneath you will find the classes and their functions:
//the BusinessModel Class
public class ModelStuff : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _modelStuff;
public ObservableCollection<DataObject> modelStuff
{
get
{
return _modelStuff;
}
set
{
_modelStuff = value;
NotifyPropertyChanged("modelStuff");
}
}
private static ModelStuff businessModel;
public static ModelStuff BusinessModel
{
get
{
if (businessModel == null)
{
businessModel = new ModelStuff();
}
return businessModel;
}
}
public ModelStuff()
{
modelStuff = new ObservableCollection<DataObject>();
modelStuff.Add(new DataObject(0));
modelStuff.Add(new DataObject(1));
modelStuff.Add(new DataObject(2));
modelStuff.Add(new DataObject(3));
modelStuff.Add(new DataObject(4));
modelStuff.Add(new DataObject(5));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the ViewModel class
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _visibleStuff;
public ObservableCollection<DataObject> visibleStuff
{
get
{
return _visibleStuff;
}
set
{
_visibleStuff = value;
NotifyPropertyChanged("visibleStuff");
}
}
private static ViewModel tvm;
public static ViewModel TVM
{
get
{
if (tvm == null)
{
tvm = new ViewModel();
}
return tvm;
}
}
public ViewModel()
{
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the TestObjects
public class DataObject
{
public int testNumber { get; set; }
public String testStr { get; set; }
public DataObject(int i)
{
testNumber = i;
testStr = "testje";
}
}
//A randomly placed button invokes this function when clicked.
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
ViewModel.TVM.visibleStuff[0].testNumber = 100;
int i1 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
//Second version but vice versa
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ViewModel.TVM.visibleStuff[0].testNumber;
ModelStuff.BusinessModel.modelStuff[0].testNumber = 100;
int i1 = ViewModel.TVM.visibleStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
Where has my reasoning gone wrong?
Why is this happening?
And more importantly, how can I prevent this behaviour?
As far as I can see, your line of code:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
is not making a copy of the underlying objects at all. It is adding the same DataObjects from the original list to a new ObservableCollection.
You need to clone the DataObjects individually and add them to the new collection. Something like this should do it:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber).Select(i => new DataObject(i.testNumber)));
something i do wrong? anyone gives some suggestions
according to msdn
Indexers of a property can be specified within square brackets following the property name where the indexer is applied. For instance, the clause Path=ShoppingCart[0] sets the binding to the index that corresponds to how your property's internal indexing handles the literal string "0". Multiple indexers are also supported.
i put Indexers of a property in my xaml
<Image Source="{Binding ImagePathList[0]}" Height="50" Width="50" Grid.Row="0" Grid.Column="0" VerticalAlignment="Top" Margin="0,7,7,0" Grid.RowSpan="2">
i do not give the viewmodel code because i am pretty sure ListImagePathList have data.
EDIT*
more detail: ImagePathList[0] is a web image url
EDIT FOR Brendan
model is Article
public class Article : INotifyPropertyChanged
{
private long _Id;
public long ID
{
get { return _Id; }
set
{
if (_Id != value)
{
_Id = value;
NotifyPropertyChanged();
}
}
}
private string _subject;
public string Subject
{
get
{
return _subject;
}
set
{
if (_subject != value)
{
_subject = value;
NotifyPropertyChanged();
}
}
}
private string _words;
public string Words
{
get
{
return _words;
}
set
{
if (_words != value)
{
_words = value;
NotifyPropertyChanged();
}
}
}
private DateTime _publishDate;
public DateTime PublishDate
{
get
{ return _publishDate; }
set
{
if (_publishDate != value)
{
_publishDate = value;
NotifyPropertyChanged();
}
}
}
public List<string> ImagePathList = new List<string>();
private string _firstImage;
public string FirstImage
{
get
{
return _firstImage;
}
set
{
if (_firstImage != value)
{
_firstImage = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ArticleViewModel is in below; All data returned from network is correct!
public class ArticleListViewModel : INotifyPropertyChanged
{
public ArticleListViewModel()
{
this.ArticleCollection = new ObservableCollection<Article>();
}
public ObservableCollection<Article> ArticleCollection
{
get;
private set;
}
public void LoadPage(int pageNumber)
{
if (pageNumber == 1)
{
this.ArticleCollection.Clear();
}
IsLoading = true;
ReadArticleList(pageNumber);
}
private async void ReadArticleList(int pageNumber)
{
try
{
List<Article> articleList = new List<Article>();
articleList = await CollectionHttpClient.GetArticlesByPageAsync(pageNumber);
this.ArticleCollection.Add(item);
}
}
catch(Exception ex)
{
if (ex.HResult == -2146233088 && ex.Message.Equals("Response status code does not indicate success: 404 ()."))
{
MessageBox.Show("The network is not set right. Internet cannot be accessed.");
}
else
{
MessageBox.Show("sorry, no data.");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The XAML code you show is fine.
There may be a problem with the DataContext. Maybe the page DataContext has not been set? Or maybe the the DataContext has changed e.g. inside an ItemTemplate
Otherise the problem is probably to do with the bound property. Try the following
private ObservableCollection<string> _imagePathList = new ObservableCollection<string>();
public ObservableCollection<string> ImagePathList {
get { return this._imagePathList; }
set {
if (this._imagePathList != value)
{
this._imagePathList = value;
// I'm going to assume you have the NotifyPropertyChanged
// method defined on the view-model
this.NotifyPropertyChanged();
}
}
}
ObservableCollection is in System.Collections.ObjectModel and is like List but if elements are added/removed then the PropertyChanged event is fired. Also note that any bound property must have a get associated with it for it to work at all.
Another possibility is that ImagePathList was not assigned to or is empty - in which case, make sure you assign to it!
In case you have not yet implemented the NotifyPropertyChanged method, here it is ...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You will also need to add INotifyPropertyChanged interface to the containing class e.g.
public class MyViewModelClass : INoftifyPropertyChanged
{
...
}
I think the problem concerns the place where your images come from. As it is said at MSDN:
You can set the Source by specifying an absolute URL (e.g. http://contoso.com/myPicture.jpg) or specify a URL relative to the XAP file of your application.
You can set this property in XAML, but in this case you are setting the property as a URI. The XAML behavior relies on underlying type conversion that processes the string as a URI, and calls the BitmapImage(Uri) constructor. This in turn potentially requests a stream from that URI and returns the image source object.
Just for test - place some images in your project and then set ImagePathList to them. See if that works (it should as I think). Or see if you can get BitmapImage(ImagePathList);
It's hard for me to say now (as I don't know how your ImagePathList looks like) what could be a reason of the failure. But if I were you, I would test this.
I would advise to use a property (or you can use converter which will also do the job):
// a property in your class
public Uri FirstImage
{
get
{
return new Uri(ImagePathList.FirstOrDefault(), UriKind.Absolute);
}
}
On the other hand if you are downloading images to IsolatedStorage then you will have to use another property, that will load BitmapImage from IS:
public BitmapImage FirstImage // getter - BitmapImage
{
get
{
if (String.IsNullOrEmpty(ImagePath[0])) return null;
BitmapImage temp = new BitmapImage();
using (IsolatedStorageFile ISF = IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream file = ISF.OpenFile(ImagePath[0], FileMode.Open, FileAccess.Read))
temp.SetSource(file);
return temp;
}
}
Hope this helps.