Let me try and illustrate an example.
I have a database table called Grades. Here is what it looks like currently.
| Id | Grade |
1 Exceeds Standards
2 Meets Standards
3 Below Standards
Now I have code that looks like this:
var myTestingExample = db.AnotherTable.Where(x => myList.Contains(x.FirstForeignKeyId))
.GroupBy(x => new { x.SecondForeignKey.Value, x.SecondTable.Name})
.Select(
x =>
new MyViewModelClass()
{
Id = x.Key.Value,
Name = x.Key.Name,
CountName = x.Count(),
CountBelowStandards = x.Count(y => y.GradingSystemId.Value == 3),
CountMeetsStandards = x.Count(y => y.GradingSystemId.Value == 2),
CountExceedsStandards = x.Count(y => y.GradingSystemId.Value == 1)
}).ToList();
Now my concern is if the user wants to create another Grade.. then I would have to manually add another property to MyViewModelClass and then have to add another line of code to the above code to get the count.. I really would want this to be dynamic so I don't have to add properties to my view model class.
How can this be achieved?
To do what you want involves introducing inheritance. See below
public class SomeViewModel : INotifyPropertyChanged
{
private ObservableCollection<IGrade> grades;
public SomeViewModel()
{
Grades = new ObservableCollection<IGrade>()
{
new Grade(1, "BelowStandards"),
new Grade(2, "MeetsStandards"),
new Grade(3, "AboveStandards")
};
}
// Add new grade here.
public void AddGrade(string name)
{
if (Grades == null)
throw new ArgumentNullException("can't be null here");
Grades.Add(new Grade(Greads.Count + 1, name));
}
public ObservableCollection<IGrade> Grades
{
get { return grades; }
set { SetField(ref grades, value, "Grades"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public interface IGrade : INotifyPropertyChanged
{
int Id { get; }
string Name { get; }
}
public class Grade : IGrade
{
public Grade(int id, string name)
{
Id = id;
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private int id;
public int Id
{
get { return id; }
set { SetField(ref id, value, "Id"); }
}
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
Then in your XAML, you bind to the relevent Grade properties. So when the user adds a new Grade, it gets displayed. Happy days.
I have not tested this code, but it should work, or at least give you the right way to go about doing what you want.
If the user wants to change the name of a grade a run time, this should also work with the above code.
Related
I found some solution of INotifyPropertyChanged wrapper but it doesnot do anything. What I am doing wrong? Name updating asynchronous but value in windows do not change. Why?* *
namespace WpfApplication1.ViewModel
{
class CustomerViewModel : INotifyPropertyChanged, IWeakEventListener
{
private readonly Customer _customer;
internal CustomerViewModel(Customer customer)
{
if (customer == null)
{
throw new ArgumentNullException("personModel");
}
_customer = customer;
NotifyPropertyChangedEventManager.AddListener(_customer, this);
Action Start = new Action(UpdateAsync);
IAsyncResult result = Start.BeginInvoke(null, null);
}
private void UpdateAsync()
{
int i = 0;
while (true)
{
System.Threading.Thread.Sleep(1000);
_customer.Name = (++i).ToString();
}
}
public string Name
{
get { return _customer.Name; }
set { _customer.Name = value; }
}
public string JobTitle
{
get { return _customer.Work; }
set { _customer.Work = value; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region IWeakEventListener Members
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
PropertyChangedEventArgs pcArgs = e as PropertyChangedEventArgs;
if (pcArgs != null)
{
OnPropertyChanged(pcArgs.PropertyName);
return true;
}
return false;
}
#endregion
}
public class NotifyPropertyChangedEventManager : WeakEventManager
{
public static NotifyPropertyChangedEventManager CurrentManager
{
get
{
var manager_type = typeof(NotifyPropertyChangedEventManager);
var manager = WeakEventManager.GetCurrentManager(manager_type) as NotifyPropertyChangedEventManager;
if (manager == null)
{
manager = new NotifyPropertyChangedEventManager();
WeakEventManager.SetCurrentManager(manager_type, manager);
}
return manager;
}
}
public static void AddListener(INotifyPropertyChanged source, IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
return;
}
public static void RemoveListener(INotifyPropertyChanged source, IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
return;
}
protected override void StartListening(object source)
{
((INotifyPropertyChanged)source).PropertyChanged += Source_PropertyChanged; return;
}
protected override void StopListening(object source)
{
((INotifyPropertyChanged)source).PropertyChanged -= Source_PropertyChanged;
return;
}
void Source_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
{ CurrentManager.DeliverEvent(sender, e); };
}
}
}
Customer
public class Customer:INotifyPropertyChanged
{
public string Name { get; set; }
public string Number { get; set; }
public string Work { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And Xaml code
<Grid>
<Label Content="{Binding Name}"></Label>
</Grid>
Instead of doing the PropertyChanged yourself, you can also use Fody.PropertyChanged. (https://github.com/Fody/PropertyChanged)
You can install it via Nuget in Visual Studio.
What is does it automaticly adds the PropertyChanged implementation when compiling.
Your code:
using PropertyChanged;
[ImplementPropertyChanged]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
}
What gets compiled:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string givenNames;
public string GivenNames
{
get { return givenNames; }
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged("GivenNames");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
There are also other features, please read them at the wiki: https://github.com/Fody/PropertyChanged/wiki
I know this question is old and doesn't exactly answer your question. I thought this would be a better solution for you.
Bindable uses a dictionary as a property store. It's easy enough to add the necessary overloads for a subclass to manage its own backing field using ref parameters.
No magic string
No reflection
Can be improved to suppress the default dictionary lookup
The code:
public class Bindable : INotifyPropertyChanged
{
private Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Gets the value of a property
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
protected T Get<T>([CallerMemberName] string name = null)
{
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T)value;
return default(T);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
protected void Set<T>(T value, [CallerMemberName] string name = null)
{
if (Equals(value, Get<T>(name)))
return;
_properties[name] = value;
OnPropertyChanged(name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
used like this
public class Item : Bindable
{
public Guid Id { get { return Get<Guid>(); } set { Set<Guid>(value); } }
}
You have to invoke OnPropertyChanged("Name");
the best place is on the Name poperty setter.
Very convenient way to implement INotifyPropertyChanged is to implement a following method in your class:
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
Then you use it like this:
private string _foo;
public string Foo
{
get { return this._foo; }
set { SetProperty(ref this._foo, value); }
}
However, because you are making a wrapper to other class, you can't use references to properties in your CustomerViewModel class. This can be worked around, but it will lead to writing a massive amount of code just to properly implement INotifyPropertyChanged.
Instead of wrapping your Customer class into some other, make it a public property instead:
private Customer _customer;
public Customer Customer
{
get { return this._customer; }
private set { SetProperty(ref this._customer, value); }
}
And in your XAML:
<Grid>
<Label Content="{Binding Customer.Name}"></Label>
</Grid>
Hope this helps.
Aside from the great answers above, you can probably achieve this behavior with a DynamicObject wrapper, wrapping the accessed class, and changing its properties on behalf, triggering a property changed event.
I haven't tried it yet (I'd probably go for Fody), but something like this pseudo might work:
public class InpcWrapperViewModel<T> : DynamicObject, INotifyPropertyChanged
{
public T Original { get; }
public InpcWrapperViewModel(T original)
{
Original = original;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
//set property of Original with reflection etc.
//raise property changed
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
//set with reflection etc.
return true;
}
}
Usage:
dynamic x = new InpcWrapperViewModel<TEntity>(entity);
window.DataContext = x;
x.FirstName = "Hello"; //triggers INPC
If you're using Prism or any other DI etc. you can create a ViewModel factory that wraps the items automatically.
Again, the above is pseudo code never tested or tried.
When using INotifyPropertyChanged it is possible to do something like this to get the name of the property where the method invoking the event was called.
public void RaisePropertyChanged([CallerMemberName] string prop = "")
{
if (PropertyChanged != null)
{
PropertyChanged(new object(), new PropertyChangedEventArgs(prop));
}
}
Is there some other type of attribute to use to also get a reference to the class that contains that property? I want to be able to call RaisePropertyChanged() from any property from any of my viewmodel classes. All my viewmodel classes derive from a base so I'm thinking I can do something like this.
public void RaisePropertyChanged([CallerMemberName] string prop = "", [CallerClassRef] VmBase base = null)
{
if (PropertyChanged != null)
{
PropertyChanged(base, new PropertyChangedEventArgs(prop));
}
}
The keyword to access the current class reference is called this:
public void RaisePropertyChanged([CallerMemberName] string prop = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
This will work no matter how many times you derive this class, this is always the instance this function was called on.
Try using Fody - PropertyChanged add in. It helps you to inject INotifyPropertyChanged implementation to IL code.
Source code :
[ImplementPropertyChanged]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
}
When compiled
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string givenNames;
public string GivenNames
{
get { return givenNames; }
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged("GivenNames");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Source code copied from : https://github.com/Fody/PropertyChanged
I have a MainPage.xaml where is ListBox and Button. When I click on the button then MainPage is navigated to AddPage.xaml. This page is for adding new items, there are two TextBoxes and submit Button. When I click on that submit Button,then data from TextBoxes are saved to XML file and then is called GoBack().
I need to refresh ListBox in my MainPage.xaml when Im going back from AddPage.xaml, but it doesnt work automaticly. How can I do that?
My MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Context> Contexts { get; private set; }
public MainViewModel()
{
this.Contexts = new ObservableCollection<Context>();
}
public bool IsDataLoaded
{
get;
private set;
}
public void LoadData()
{
try
{
var file = IsolatedStorageFile.GetUserStoreForApplication();
XElement xElem;
using (IsolatedStorageFileStream read = file.OpenFile("contexts.xml", FileMode.Open))
{
xElem = XElement.Load(read);
}
var contexts = from context in xElem.Elements("Context")
orderby (string)context.Element("Name")
select context;
foreach (XElement xElemItem in contexts)
{
Contexts.Add(new Context
{
Name = xElemItem.Element("Name").Value.ToString(),
Note = xElemItem.Element("Note").Value.ToString(),
Created = xElemItem.Element("Created").Value.ToString()
});
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
this.IsDataLoaded = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and Context.cs
public class Context : INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private string _note;
public string Note
{
get
{
return _note;
}
set
{
if (value != _note)
{
_note = value;
NotifyPropertyChanged("Note");
}
}
}
private string _created;
public string Created
{
get
{
return _created;
}
set
{
if (value != _created)
{
_created = value;
NotifyPropertyChanged("Created");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You'll need to tell the main page that there is new data to reload.
At it's simplest, something like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.NavigationMode == NavigationMode.Back)
{
(this.DataContext as MainViewModel).LoadData();
}
}
Tip: You aren't raising a property changed notification for your View Model's properties.
On the load event of the MainPage, call LoadData. You should also clear the observable collection when you call the LoadData method before adding anything to it because simply loading the data will cause duplicate entries in your collection.
im learning the ropes in LINQ and created a DB and been trying to update a table record but i keep getting the member P.roject1.ChildDBdetails.Id has no supported translation to SQL.
this is the query:
public void UpdateChildrecord()
{
using (ChildDBDataContext ChildDB = new ChildDBDataContext(Con_String))
{
IQueryable<ChildDBdetails> query =
from c in ChildDB.ChildDBdetails
where c.Id == id
select c;
ChildDBdetails updaterecord = query.FirstOrDefault();
updaterecord.Team = newteam;
ChildDB.SubmitChanges();
}
}
i'm a newbie to linq-to_sql and don't really understand why this is happening.how can i fix the error?
thanks.
TableModel:
[Table]
public class ChildDBdetails : INotifyPropertyChanged, INotifyPropertyChanging
{
[Column(IsPrimaryKey = true, IsDbGenerated = false, CanBeNull = false)]
private int id;
public int Id
{
get { return id; }
set
{
NotifyPropertyChanging("Id");
id = value;
NotifyPropertyChanged("Id");
}
}
[Column]
private string team;
public string Team
{
get { return team; }
set
{
NotifyPropertyChanging("Team");
team = value;
NotifyPropertyChanged("Team");
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Implementation of INotifyPropertyChanging
public event PropertyChangingEventHandler PropertyChanging;
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
I'm new to Windows Phone 7 development and I'm in trouble with Linq to SQL. I'm trying to update an object but it just don't work. I get the object I want to update and modify the property I want, but when I call SaveChanges, the data it's not updated on database.
I already downloaded the database using ISETool and checked that data isn't updated at all.
What's strange is the querying and inserting methods works fine, but I don't know why updating it's not.
Here's the entity and the updating method code:
[Table]
public class Entrada : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _Id;
[Column]
private int _DiaId;
[Column]
private int _ProjetoId;
private EntityRef<Dia> _Dia;
private EntityRef<Projeto> _Projeto;
private DateTime _Chegada;
private DateTime? _Saida;
[Column(IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, DbType = "INT NOT NULL Identity")]
public int Id
{
get
{
return _Id;
}
set
{
if (value != _Id)
{
NotifyPropertyChanging("Id");
_Id = value;
NotifyPropertyChanged("Id");
}
}
}
[Column(CanBeNull=false)]
public DateTime Chegada
{
get
{
return _Chegada;
}
set
{
if (value != _Chegada)
{
_Chegada = value;
NotifyPropertyChanged("Chegada");
}
}
}
[Association(Storage = "_Dia", ThisKey = "_DiaId", OtherKey="Id", IsForeignKey=true)]
public Dia Dia
{
get { return _Dia.Entity; }
set
{
NotifyPropertyChanging("Dia");
_Dia.Entity = value;
if (value != null)
{
_DiaId= value.Id;
}
NotifyPropertyChanging("Dia");
}
}
[Column(CanBeNull=true)]
public DateTime? Saida
{
get
{
return _Saida;
}
set
{
if (value != _Saida)
{
_Saida = value;
NotifyPropertyChanged("Saida");
}
}
}
[Association(Storage = "_Projeto", ThisKey = "_ProjetoId", OtherKey = "Id", IsForeignKey = true)]
public Projeto Projeto
{
get
{
return _Projeto.Entity;
}
set
{
NotifyPropertyChanging("Projeto");
_Projeto.Entity = value;
if (value != null)
{
_ProjetoId = value.Id;
}
NotifyPropertyChanging("Projeto");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
private void NotifyPropertyChanged(String propertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
}
//UPDATE CODE:
var query = (from e in timeSheetDB.Entradas where e.Dia.Id == this.Dia.Id && (!e.Saida.HasValue) select e);
var entrada = query.FirstOrDefault();
if (entrada != null)
{
entrada.Saida = DateTime.Now;
}
timeSheetDB.SubmitChanges();
I also checked the GetChangeSet().Updates.Count(), but it's always 0. I hope you can help me :-)
thank you guys!
It appears to be the case that you're not raising the PropertyChanging event for the Saida property; this event is important for LINQ-to-SQL, so I'd suggest updating all of your members that represent columns to raise it (in addition to the PropertyChanged event)