I'm dipping my feet into Windows Phone development and are starting to get to terms with some of the features of Silverlight on WP, but I'm struggling with XML:
I'm trying to serialize some objects into an XML and then read the said XML and serialize it into objects again. Then I'll use that to populate a listbox by putting an ObservableCollection as the ItemsSource of the listbox.
I've already made sure that the databinding works properly; if I just generate the objects and put them into an Observable Collection and then put that as the ItemsSource, there are no problems. It would seem that it's the XML part of my code that's faulting. Everything compiles and executes nice enough, but the listbox remains empty :(
This code executes as the app launches (not very effective but works for my testing):
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<Quote> quotes = new ObservableCollection<Quote>();
for (int i = 0; i < 10; i++)
{
Quote quote = new Quote()
{
Author = "Author #" + i.ToString(),
QuoteText = "This is quote #" + i.ToString(),
};
quotes.Add(quote);
}
XmlWriterSettings xmlwrtrstngs = new XmlWriterSettings();
xmlwrtrstngs.Indent = true;
using(IsolatedStorageFile isostrg = IsolatedStorageFile.GetUserStoreForApplication())
{
using(IsolatedStorageFileStream isoflstrm = isostrg.OpenFile("Quotes.xml", FileMode.Create))
{
XmlSerializer xmlsrlzr = new XmlSerializer(typeof(QuoteCollection));
using(XmlWriter xmlwrtr = XmlWriter.Create(isoflstrm, xmlwrtrstngs))
{
xmlsrlzr.Serialize(xmlwrtr, quoteCollection);
}
}
}
loadData();
}
void loadData()
{
try
{
using(IsolatedStorageFile isostrg = IsolatedStorageFile.GetUserStoreForApplication())
{
using(IsolatedStorageFileStream isoflstrm = isostrg.OpenFile("Quotes.xml", FileMode.Open))
{
XmlSerializer xmlsrlzr = new XmlSerializer(typeof(QuoteCollection));
QuoteCollection quoteCollectionFromXML = (QuoteCollection)xmlsrlzr.Deserialize(isoflstrm);
LstBx.ItemsSource = quoteCollectionFromXML.Quotes;
}
}
}
catch(Exception)
{
Console.Write("Something went wrong with the XML!");
}
}
QuoteCollection
public class QuoteCollection : INotifyPropertyChanged
{
ObservableCollection<Quote> quotes;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Quote> Quotes
{
get { return quotes; }
set
{
if(quotes != value)
{
quotes = value;
raisePropertyChanged("Quotes");
}
}
}
protected virtual void raisePropertyChanged(string argPropertyChanged)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(argPropertyChanged));
}
}
}
Quote
public class Quote : INotifyPropertyChanged
{
string author;
string quoteText;
public event PropertyChangedEventHandler PropertyChanged;
public string Author
{
get
{
return author;
}
set
{
if(author != value)
{
author = value;
onPropertyChanged("Author");
}
}
}
public string QuoteText
{
get
{
return quoteText;
}
set
{
if(quoteText != value)
{
quoteText = value;
onPropertyChanged("QuoteText");
}
}
}
protected virtual void onPropertyChanged(string argProperty)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(argProperty));
}
}
}
Any insight would be most appreciated :)
You are serializing using QuoteCollection as the type, but actually writing out an ObservableCollection<Quote>.
Forgot to put the ObservableCollection into the QuoteCollection instance I'd made previously (not shown in the code posted).
Related
Given:
public class MyClass : INotifyPropertyChanged
{
public List<string> _TestFire = new List<string>();
string _StringProp;
public string StringProp
{
get
{
return _StringProp;
}
set
{
if (_StringProp != value)
{
_StringProp = value;
RaisePropertyChanged("StringProp");
_TestFire.Add("fired " + DateTime.Now);
}
}
}
}
When you serialize and then deserialize this class, it fires the RaisePropertyChanged event, which is undesirable. Is it possible to prevent this event from being fired when the class gets deserialized?
var MyclassInstance = new MyClass() { StringProp = "None" };
MyclassInstance._TestFire.Clear(); // Clear property change history
var serobj = JsonConvert.SerializeObject();
var newitem = JsonConvert.DeserializeObject<MyClass>(serobj);
// newitem._TestFire.Count == 1, the set method was executed
Is there a way to get a bool value if the class is being deserialized?
So then I could do:
set
{
if (_StringProp != value)
{
_StringProp = value;
if (!Deserializing)
{
RaisePropertyChanged("StringProp");
_TestFire.Add("fired " + DateTime.Now);
}
}
}
Yes, you can do what you want by implementing the OnDeserializing and OnDeserialized serialization callback methods in your class. In the OnDeserializing method, set your private _isDeserializing variable to true, and in OnDeserialized set it back to false. I would recommend doing the _isDeserializing check inside the RaisePropertyChanged method so you don't have duplicate code inside every property.
So you would end up with something like this:
public class MyClass : INotifyPropertyChanged
{
public List<string> _TestFire = new List<string>();
string _StringProp;
public string StringProp
{
get
{
return _StringProp;
}
set
{
if (_StringProp != value)
{
_StringProp = value;
RaisePropertyChanged("StringProp");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
// don't raise the event if the property is being changed due to deserialization
if (_isDeserializing) return;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
_TestFire.Add(propertyName + " was fired " + DateTime.Now);
}
bool _isDeserializing = false;
[OnDeserializing]
internal void OnDeserializingMethod(StreamingContext context)
{
_isDeserializing = true;
}
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
_isDeserializing = false;
}
}
Working demo: https://dotnetfiddle.net/QkOcF4
Another way you could solve this problem is to mark your public properties with [JsonIgnore] and then mark the corresponding backing fields with [JsonProperty], specifying the property name to use in the JSON. This would allow Json.Net to set the backing fields directly and not execute any of the mutator logic.
public class MyClass : INotifyPropertyChanged
{
public List<string> _TestFire = new List<string>();
[JsonProperty("StringProp")]
string _StringProp;
[JsonIgnore]
public string StringProp
{
get
{
return _StringProp;
}
set
{
if (_StringProp != value)
{
_StringProp = value;
RaisePropertyChanged("StringProp");
_TestFire.Add("StringProp was fired " + DateTime.Now);
}
}
}
...
}
Working demo: https://dotnetfiddle.net/jc7wDu
Please assume this entire question deals in code, without any XAML.
I have a static ObservableCollection named myStaticList. It's a part of a non-static class named myClass.
public class myClass
{
public static ObservableCollection<CheckBoxStructure> myStaticList { get; set; }
static myClass()
{
myStaticList = new ObservableCollection<CheckBoxStructure>();
}
}
And the definition of CheckBoxStructure:
public class CheckBoxStructure
{
public string Description { get; set; }
public bool IsSelected { get; set; }
}
In addition, there's an array of checkboxes called checkBoxArray[], holding 3 elements. each checkbox has as content a textbox.
What I want to do is programmatically bind (two-way) these two, in such a manner that the IsChecked property of the checkboxes in the checkBoxArray[] array will bind to the IsSelected property of the myStaticList's CheckBoxStructure, and similarly so between the text of the textboxes inthe checkboxes' content and the Description property of the myStaticList's CheckBoxStructure.
In addition, I would like to avoid using loops, since it is preferable that this two lists will update each other if they change in size.
How is this possible?
Thanks!
Using XAML, an easy way would be to the declare an ItemsControl and a DataTemplate for it so that you can have a UserControl (CheckBox and TextBox inside) with its DataContext being a CheckBoxStructure. This way the bindings work between CheckBox.IsChecked and IsSelected property and between TextBox.Text and Description property.
If you need to this only in code then you would have to create same behavior (ItemsControl with a DataTemplate). You have at least 2 options
1.
DataTemplate template = new DataTemplate();
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
FrameworkElementFactory childFactory = new FrameworkElementFactory(typeof(CheckBox));
childFactory.SetBinding(CheckBox.IsChecked, new Binding("IsSelected"));
factory.AppendChild(childFactory);
childFactory = new FrameworkElementFactory(typeof(TextBox));
childFactory.SetBinding(Label.ContentProperty, new Binding("Description"));
factory.AppendChild(childFactory);
2.
MemoryStream sr = null;
ParserContext pc = null;
string xaml = string.Empty;
xaml = "<DataTemplate><StackPanel><TextBlock Text="{Binding Description"/><CheckBox IsChecked="{Binding IsSelected"/></StackPanel></DataTemplate>";
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc = new ParserContext();
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
DataTemplate datatemplate = (DataTemplate)XamlReader.Load(sr, pc);
this.Resources.Add("dt", datatemplate);
Later edit, after discussion from comments; this example works only one way of binding but is easily to make it two ways. Please note that this is only a trivial example of a concept and is not complete: you need to modify the list classes to suit how you wish for objects to be paired, you may need to add more guards for corner cases, you may need to make it thread safe and so on...
First the basic binding objects:
class Binder
{
public Binder()
{
_bindings = new Dictionary<string, List<string>>();
}
private INotifyPropertyChanged _dataContext;
public INotifyPropertyChanged DataContext
{
get { return _dataContext; }
set
{
if (_dataContext != null)
{
_dataContext.PropertyChanged -= _dataContext_PropertyChanged;
}
_dataContext = value;
_dataContext.PropertyChanged += _dataContext_PropertyChanged;
}
}
void _dataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_bindings.ContainsKey(e.PropertyName))
{
var bindableType = _dataContext.GetType();
var bindableProp = bindableType.GetProperty(e.PropertyName);
if (bindableProp == null)
{
return;
}
var binderType = this.GetType();
foreach (var binderPropName in _bindings[e.PropertyName])
{
var binderProp = binderType.GetProperty(binderPropName);
if (binderProp == null)
{
continue;
}
var value = bindableProp.GetValue(_dataContext);
binderProp.SetValue(this, value);
}
}
}
Dictionary<string, List<string>> _bindings;
public void AddBinding(string binderPropertyName, string bindablePropertyName)
{
if (!_bindings.ContainsKey(bindablePropertyName))
{
_bindings.Add(bindablePropertyName, new List<string>());
}
_bindings[bindablePropertyName].Add(bindablePropertyName);
}
}
class Bindable : INotifyPropertyChanged
{
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then the holding lists for them:
class BindableList<T> : List<T> where T : Bindable
{
public event Action<T> ItemAdded;
public new void Add(T item)
{
base.Add(item);
NotifyItemAdded(item);
}
private void NotifyItemAdded(T item)
{
if (ItemAdded != null)
{
ItemAdded(item);
}
}
}
class BinderList<T> : List<T> where T : Binder
{
public BinderList()
{
_bindingRules = new Dictionary<string, string>();
}
private BindableList<Bindable> _dataContextList;
public BindableList<Bindable> DataContextList
{
get { return _dataContextList; }
set
{
if (_dataContextList != null)
{
_dataContextList.ItemAdded -= _dataContextList_ItemAdded;
}
_dataContextList = value;
_dataContextList.ItemAdded += _dataContextList_ItemAdded;
}
}
void _dataContextList_ItemAdded(Bindable obj)
{
foreach (var pair in _bindingRules)
{
this[Count-1].AddBinding(pair.Key, pair.Value);
this[Count - 1].DataContext = obj;
}
}
private Dictionary<string, string> _bindingRules;
public void AddBindingRule(string binderPropertyName, string bindablePropertyName)
{
_bindingRules.Add(binderPropertyName, bindablePropertyName);
}
}
Now the actual classes with properties:
class BinderElement : Binder
{
private string _description;
public string Description
{
get { return _description; }
set { _description = value; }
}
}
class BindableElement : Bindable
{
private string _description;
public string Description
{
get
{
return _description;
}
set
{
_description = value;
NotifyPropertyChanged("Description");
}
}
}
And an example to use them:
static void Main(string[] args)
{
var bindableList = new BindableList<Bindable>();
var binderList = new BinderList<BinderElement>()
{
new BinderElement(),
new BinderElement()
};
binderList.DataContextList = bindableList;
binderList.AddBindingRule("Description", "Description");
bindableList.Add(new BindableElement());
bindableList.Add(new BindableElement());
((BindableElement)bindableList[1]).Description = "This should arrive in BinderElement Description property";
Console.WriteLine(binderList[1].Description);
Console.ReadLine();
}
I am hoping to get some pointers on what I am missing in my code.
I have a text box bound to a object property that is an item in the list, and that value doesnt update on the form if I request another item in the list.
To illustrate with example below:
txtGain value is populated after openJSONRequestFileToolStripMenuItem_Click fuction
Once I select something different in cmbSignals combobox, I expect the txtGain value to become updated since SelectedChannel is updated as well, which in turn updates the selectedindex but it doesn't happen.
Basically I want to have my txtGain value updated based on what I select in the cmbSignals. Obviously the binding is there so that I can modify the value in the text box and have it be updated in the property its bound to.
I suspect that I have to somehow force update the bindings but not sure how to do that. Any help would be appreciated.
public partial class MainForm : Form
{
private MyData req;
public MainForm()
{
InitializeComponent();
cmbSignals.DisplayMember = "Name";
cmbSignals.ValueMember = "Value";
}
private void openJSONRequestFileToolStripMenuItem_Click(object sender, EventArgs e)
{
string text = File.ReadAllText("sample.json");
req = new MyData(JsonConvert.DeserializeObject<SerializedRequest>(text));
cmbSignals.DataSource = req.SignalNames;
cmbSignals.SelectedValue = req.SelectedChannel;
SetBindings();
}
private void SetBindings()
{
txtGain.DataBindings.Add(new Binding("Text", req, "Gain"));
}
private void cmbSignals_SelectedValueChanged(object sender, EventArgs e)
{
req.SelectedChannel = Convert.ToInt32(cmbSignals.SelectedValue);
}
}
public class MyData : INotifyPropertyChanged
{
private SerializedRequest Data = new SerializedRequest();
private int selectedIndex = 0;
public int SelectedChannel
{
get
{
return selectedIndex + 1;
}
set
{
this.selectedIndex = value - 1;
}
}
public string Gain
{
get
{
return Data.signals[selectedIndex].gain;
}
set
{
Data.signals[selectedIndex].gain = value;
OnPropertyChanged("Gain");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public List<SignalsCmbItem>SignalNames
{
get
{
List<SignalsCmbItem>channels = new List<SignalsCmbItem>();
for(int i = 0; i<Data.signals.Count;i++)
{
channels.Add(new SignalsCmbItem { Value = i + 1, Name = i+1 + " - " + Data.signals[i].label });
}
return channels;
}
}
}
Pretty annoying "feature", isn't it?.
But no worries, to get around this, add one line of code inside your cmbSignals_SelectedValueChanged(sender, e) method, after you change value of req.SelectedChannel.
txtGain.BindingContext = new BindingContext();
My Class relation is like this Model:
Class MainModel
{
data data1 = new data();
public override string LFE
{
get { return data1.lnf.ToString(); }
set { data1.lnf = Convert.ToByte(value); }
}
public override UInt16 GetRsBValue(int index)
{
return (byte)this.data1.CConfig[index].Bline;
}
public override void SetRsBValue(UInt16 value, int index)
{
byte[] arr = BitConverter.GetBytes(value);
this.data1.CConfig[index].Bline = arr[0];
}
}
Class data
{
public byte Bline
{
get { return this.bline; }
set { this.bline = value; }
}
public byte lnf
{
get { return this.ln_frequency; }
set { this.ln_frequency = value; }
}
}
Class ViewModel : INotifyPropertyChange
{
public UInt16 Rschange
{
get
{
return this.eObj.GetRsBValue(this.index);
}
set
{
this.eObj.SetRsBValue(value, this.Index);
this.OnPropertyChanged(new PropertyChangedEventArgs("Rschange"));
this.eObj.writedata(DefaultFileName);
}
}
public string LF
{
get
{
return this.eObj.LFE;
}
set
{
this.eObj.LFE = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(" LF"));
}
}
}
In Model side I have created instance of data class in main Model.
I'm getting data from other application to my application. I'm getting that updated value till data class but it's not showing that value in MainModel. So It's not updating mu UI at all. Please tell me how can I update my UI when I'm getting value from other application.
P.S: I don't want to create Model class instance in ViewModel side and I have 10 properties and 10 method like this in my class.
You have a typo in your call to property changed in your LF property.
// note the extra space V
this.OnPropertyChanged(new PropertyChangedEventArgs(" LF"));
If you create your property changed handler like this:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You can prevent such typos by using the following syntax:
public string LF
{
get
{
return this.eObj.LFE;
}
set
{
this.eObj.LFE = value;
this.OnPropertyChanged();
}
}
Problem
Problem only comes when I try to insert. However I could browse through records. When I insert a new record, the UserControl does not consider the value I typed in, instead it stores as null. I basically think on New record, it does not sync.
The Form Layout
Purpose of UserControl
The DB stores Field value in the form of JKB-932340094VN00 where I use my UserControl for split Values by - and display it in 2 TextBox in it. So one TextBox will have the Value of JKB and other will have 932340094VN00
UserControl is as follows:
public partial class ucClientAccountIDParser : UserControl, INotifyPropertyChanged
{
public ucClientAccountIDParser()
{
InitializeComponent();
id = "JKB-821230063VN00";
txtClientAccountID.TextChanged += new EventHandler(clientaccountIDChanged);
txtCustodyID.TextChanged += new EventHandler(custodyIDChanged);
}
private string _clientaccountID = string.Empty;
public string ClientaccountID
{
get { return _clientaccountID; }
set
{
_clientaccountID = value;
txtClientAccountID.Text = this._clientaccountID;
}
}
private string _custodyID = string.Empty;
public string CustodyID
{
get { return _custodyID; }
set
{
_custodyID = value;
txtCustodyID.Text = this._custodyID;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public string id = string.Empty;
[System.ComponentModel.Bindable(true)]
public string Text
{
get
{
return id;
}
set
{
id = value;
if (id != null)
{
string[] aVal = id.Split('-');
if (aVal.Length > 1)
{
this.CustodyID = aVal[0];
this.ClientaccountID = aVal[1];
}
else
{
this.ClientaccountID = id;
}
}
else
{
this.CustodyID = string.Empty;
this.ClientaccountID = string.Empty;
}
NotifyPropertyChanged(id);
}
}
public void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
private void clientaccountIDChanged(object sender, EventArgs e)
{
this.id = string.Format("{0}-{1}", txtCustodyID.Text, (sender as TextBox).Text);
NotifyPropertyChanged(this.id);
}
private void custodyIDChanged(object sender, EventArgs e)
{
this.id = string.Format("{0}-{1}", (sender as TextBox).Text, txtClientAccountID.Text);
NotifyPropertyChanged(this.id);
}
}
This is how I bind it:
try
{
this.Init(null);
this.Text = "General Account Add/Change";
objTable = new ClientAccountBusinessTable();
this.PrimaryDataAdapters = new ClientAccountBusinessTable[1] { objTable };
this.PrimaryDataGridView = null;
objTable.KeyDBField = "CLIENTID";
PrimaryBindingSource = new BindingSource[1] { bindingSource1 };
PrimaryDataSet.Tables.Add(objTable.GetBusinessTable());
SetKeyDBControl(new Control[1] { ucClientAccountIDParser1 }, new string[1] { "CLIENTID" });
ucClientAccountIDParser1.DataBindings.Clear();
ucClientAccountIDParser1.DataBindings.Add(new Binding("Text", this.bindingSource1, "CLIENTID"));
bindingSource1.DataMember = this.PrimaryDataSet.Tables[0].TableName;
bindingSource1.DataSource = this.PrimaryDataSet;
}
catch (Exception ex)
{
objTable = null;
throw ex;
}
I cant figure out why it takes null. Let me know if I carried out design of UserControl in proper way.
I hope anybody can help me out.