I make a following sample to attempt to dynamic update my UI while long lasting operation running, but the UI hang while it runs.
Here is my entity class:
public class ClsTest : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set
{
if (string.IsNullOrEmpty(value) && value == _text)
return;
_text = value;
NotifyPropertyChanged(() => Text);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged<T>(Expression<Func<T>> property)
{
if (PropertyChanged == null)
return;
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
}
}
And here is my binding code in my main form_load
ClsTest x = new ClsTest();
x.Text = "yes";
txtValue.DataBindings.Add("Text", x, "Text", false, DataSourceUpdateMode.Never);
When I run following code in a button click method, it hangs:
for (int i = 0; i < 20; i++)
{
x.Text = i.ToString();
System.Threading.Thread.Sleep(1000);
}
How can I resolve this problem?
Related
Thats my first project using MVVM , MVVM light.
I have a listbox, that gets refreshed from the PersonList Observable collection, adding and removing refresh it normal. the problem is when editing an item.
I looked for all the solutions for this problem, nothing worked, which make me think that I missed something.
so here is the code :
public class AdminViewModel : ApplicationPartBaseViewModel
{
private ObservableCollection<Person> personList;
public AdminViewModel()
{
this.context = new Entities();
this.SavePersonCommand = new RelayCommand(() => this.SavePerson ());
this.PersonList = new ObservableCollection<Peson>(context.Person.OrderBy(o => o.PersonName).ToList());
}
public ObservableCollection<Person> PersonList
{
get
{
return personList;
}
set
{
this.personList = value;
RaisePropertyChanged("PersonList");
}
}
private void SavePerson()
{
//Add and update code here
this.context.SaveChanges();
RaisePropertyChanged("PersonList");
}
}
Person Class is Autogenerated template from the DataModel edmx
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
public partial class Person
{
#region Primitive Properties
public virtual int PersonId
{
get;
set;
}
public virtual string PersonName
{
get;
set;
}
public virtual Nullable<int> PersonAge
{
get;
set;
}
#endregion
#region Navigation Properties
public virtual ICollection<Humans> Humans
{
get
{
if (_human == null)
{
var newCollection = new FixupCollection<Human>();
newCollection.CollectionChanged += FixupHuman;
_human = newCollection;
}
return _human;
}
set
{
if (!ReferenceEquals(_human, value))
{
var previousValue = _human as FixupCollection<Human>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupHuman;
}
_human = value;
var newValue = value as FixupCollection<Human>;
if (newValue != null)
{
newValue.CollectionChanged += FixupAssets;
}
}
}
}
private ICollection<Human> _human;
#endregion
#region Association Fixup
private void FixupHuman(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Human item in e.NewItems)
{
if (!item.Person.Contains(this))
{
item.Person.Add(this);
}
}
}
if (e.OldItems != null)
{
foreach (Human item in e.OldItems)
{
if (item.Person.Contains(this))
{
item.Person.Remove(this);
}
}
}
}
#endregion
}
I thought that MVVM light update the item when I call RaisePropertyChanged.
I am so confused.
Thanks in advance.
First option is try to get your auto-generated class to implement INPC if you can. Have a look at Fody.PropertyChanged
If that's not possible, since it does have it's properties as "virtual", we can over-ride them in a derived class such as
public class ObservablePerson : Person, INotifyPropertyChanged {
public override int PersonId {
get {
return base.PersonId;
}
set {
base.PersonId = value;
OnPropertyChanged();
}
}
public override string PersonName {
get {
return base.PersonName;
}
set {
base.PersonName = value;
OnPropertyChanged();
}
}
public override int? PersonAge {
get {
return base.PersonAge;
}
set {
base.PersonAge = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in your AdminViewModel work with objects of type ObservablePerson than Person
I am trying to create a new user control as a generic lookup form opener.
lkpControl.FormToOpen="FrmProductList";
lkpControl.ReturnValueVariableName="ProductCode";
lkpControl.ShowThatForm();
if dialog result is OK:
lkpControl.txtValue=lkpControl.GetSelectedValue();
this control will open preconfigured (or specified in design time) form from its name and will return the selected item in opened form back to its textbox. Simply like getting the path from folder browser dialog.
simply a generic form opener and value getter of selected item in opened form.
I would appreciate if you could direct me to any solution or a path to follow.
Thanks in advance
UPDATE:
I've solved it by this:
public partial class LookupButton : UserControl
{
[Description("Type of the Form To Open: typeof(LookupButtonTest.Form2)")]
[Category("Custom")]
public Type FormToOpen { get; set; }
[Description("Name Of the public property to get return value from opened form.")]
[Category("Custom")]
public string PropertyToGet { get; set; }
public LookupButton()
{
InitializeComponent();
}
private void btnOpenForm_Click(object sender, EventArgs e)
{
if (FormToOpen == null)
{
throw new ArgumentNullException("FormToOpen");
}
if (PropertyToGet.Length <= 0)
{
throw new ArgumentNullException("PropertyToGet");
}
Form objForm = (Form)Activator.CreateInstance(FormToOpen);
if (objForm.ShowDialog() == DialogResult.OK)
{
bool propertyFound = false;
PropertyInfo[] props = objForm.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo p in props)
{
if (p.Name == PropertyToGet)
{
txtReturnVal.Text = p.GetValue(objForm, null).ToString();
propertyFound = true;
}
}
if (!propertyFound)
{
throw new ArgumentNullException("PropertyToGet");
}
}
}
}
Something like this:
public static class ShowFormAs
{
public static T1 ShowForm<T1>(string fullTypeName, string propertyName)
where T1: class
{
return string.IsNullOrEmpty(fullTypeName)
? default(T1) :
ShowForm<T1>(System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(fullTypeName) as Form, propertyName);
}
public static T1 ShowForm<T, T1>(string propertyName)
where T: Form, new ()
where T1: class
{
return ShowForm<T1>(new T(), propertyName);
}
private static T1 ShowForm<T1>(Form form, string propertyName)
where T1: class
{
if (form == null)
{
return default(T1);
}
if (form.ShowDialog() == DialogResult.OK)
{
var property = form.GetType().GetProperties().FirstOrDefault(o => string.CompareOrdinal(o.Name, propertyName) == 0);
if (property == null)
{
return default(T1);
}
return property.GetValue(form, null) as T1;
}
return default(T1);
}
}
Usage:
var x = ShowFormAs.ShowForm<Form2, string>("TTT");
var z = ShowFormAs.ShowForm<string>("StackOverflow.Form2", "TTT");
The Form2 class is:
namespace StackOverflow
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public string TTT { get { return "Ololo"; } }
}
}
And result is:
I am trying to use the calculated columns to display in my grid.
I have a partial class automatically generated by EF code generator with three properties: and i am trying to creating another partial class and add calculated field there for e.g.
Public partial class Employee
{
public decimal? totalSalary
{
get
{
return salary*wagerate+bonus;
}
}
}
It works fine for the first time but does not work when the salary/bonus/hours are changed. I am using these fields inside a grid
Here is my code generated by EF entity generator
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization;
using System.ComponentModel.DataAnnotations;
namespace Employees.Contract
{
[DataContract(IsReference = true)]
[KnownType(typeof(Department))]
[KnownType(typeof(PropertyType))]
public partial class Employee: IObjectWithChangeTracker, INotifyPropertyChanged,IDataErrorInfo
{
[NonSerialized]
private CLOS.Contract.Validation.DataErrorInfoSupport dataErrorInfoSupport;
public Employee()
{
dataErrorInfoSupport = new CLOS.Contract.Validation.DataErrorInfoSupport(this);
Init();
}
partial void Init();
string IDataErrorInfo.Error { get { return dataErrorInfoSupport.Error; } }
string IDataErrorInfo.this[string memberName] { get { return dataErrorInfoSupport[memberName]; } }
#region Primitive Properties
[DataMember]
public Nullable<decimal> Salary
{
get { return _salary; }
set
{
if (_salary != value)
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
private Nullable<decimal> _salary;
[DataMember]
public Nullable<decimal> WageRate
{
get { return _wageRate; }
set
{
if (_wageRate != value)
{
_wageRate = value;
OnPropertyChanged("WageRate");
}
}
}
private Nullable<decimal> _wageRate;
[DataMember]
public Nullable<decimal> Bonus
{
get { return _bonus; }
set
{
if (_bonus != value)
{
_bonus = value;
OnPropertyChanged("Bonus");
}
}
}
private Nullable<decimal> _bonus;
#endregion
#region Navigation Properties
[DataMember]
public Department Department
{
get { return _department; }
set
{
if (!ReferenceEquals(_department, value))
{
var previousValue = _department;
_department = value;
OnNavigationPropertyChanged("Department");
}
}
}
private Borrower _department;
[DataMember]
public PropertyType PropertyType
{
get { return _propertyType; }
set
{
if (!ReferenceEquals(_propertyType, value))
{
var previousValue = _propertyType;
_propertyType = value;
OnNavigationPropertyChanged("PropertyType");
}
}
}
private PropertyType _propertyType;
#endregion
#region ChangeTracking
protected virtual void OnPropertyChanged(String propertyName)
{
if (ChangeTracker.State != ObjectState.Added && ChangeTracker.State != ObjectState.Deleted)
{
ChangeTracker.State = ObjectState.Modified;
}
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected virtual void OnNavigationPropertyChanged(String propertyName)
{
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged{ add { _propertyChanged += value; } remove { _propertyChanged -= value; } }
private event PropertyChangedEventHandler _propertyChanged;
private ObjectChangeTracker _changeTracker;
[DataMember]
public ObjectChangeTracker ChangeTracker
{
get
{
if (_changeTracker == null)
{
_changeTracker = new ObjectChangeTracker();
_changeTracker.ObjectStateChanging += HandleObjectStateChanging;
}
return _changeTracker;
}
set
{
if(_changeTracker != null)
{
_changeTracker.ObjectStateChanging -= HandleObjectStateChanging;
}
_changeTracker = value;
if(_changeTracker != null)
{
_changeTracker.ObjectStateChanging += HandleObjectStateChanging;
}
}
}
private void HandleObjectStateChanging(object sender, ObjectStateChangingEventArgs e)
{
if (e.NewState == ObjectState.Deleted)
{
ClearNavigationProperties();
}
}
protected bool IsDeserializing { get; private set; }
[OnDeserializing]
public void OnDeserializingMethod(StreamingContext context)
{
IsDeserializing = true;
}
[OnDeserialized]
public void OnDeserializedMethod(StreamingContext context)
{
dataErrorInfoSupport = new CLOS.Contract.Validation.DataErrorInfoSupport(this);
IsDeserializing = false;
ChangeTracker.ChangeTrackingEnabled = true;
}
protected virtual void ClearNavigationProperties()
{
Department = null;
PropertyType = null;
}
#endregion
}
}
It also works if i put OnPropertyChanged("Salary") in Hours,Wage,Overtime Property in EF Generated class (which is not a good idea) because if the class gets regenerated , my code will be wiped out
Any help is appreciated. (Sorry for the formatting , this is my first question)
Thanks
In the partial class use the partial Init method to subscribe to the PropertyChanged event and when either the salary, wagerate or bonus property changes notify clients of the change of the totalSalary.
This way you do not need to modify the generated code. (that is why the Init method is partial).
public partial class Employee
{
partial void Init()
{
_propertyChanged += PropertyChangedHandler;
}
void PropertyChangedHandler(object sender, PropertyChangedEventArgs args)
{
if(args.PropertyName == "salary" ||
args.PropertyName == "wagerate" ||
args.PropertyName == "bonus")
{
OnPropertyChanged("totalSalary");
}
}
public decimal? totalSalary
{
get
{
return salary * wagerate + bonus;
}
}
}
this is why MVVM is a popular design pattern, if you wrap your Employee (a Model) in a new class (a ViewModel), it will be easier to customise before you hand it to the grid (the View).
However, a hacky way to get your current code working would be to attach to the current PropertyChanged event in your partial class and call OnPropertyChanged("Salary") if the current property name matches one of the dependent properties (watch out for recursion!)
I have built a base class for my view model(s). Here is some of the code:
public class BaseViewModel<TModel> : DependencyObject, INotifyPropertyChanged, IDisposable, IBaseViewModel<TModel>, IDataErrorInfo
{
public TModel Model { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (this._disposed)
{
return;
}
if (disposing)
{
this.Model = default(TModel);
}
this._disposed = true;
}
}
Okay, so I thought, let's add some validation to the base class, which led me to the following article: Prism IDataErrorInfo validation with DataAnnotation on ViewModel Entities. So I added the following methods / properties (IDataErrorInfo) to my base class:
string IDataErrorInfo.Error
{
get { return null; }
}
string IDataErrorInfo.this[string columnName]
{
get { return ValidateProperty(columnName); }
}
protected virtual string ValidateProperty(string columnName)
{
// get cached property accessors
var propertyGetters = GetPropertyGetterLookups(GetType());
if (propertyGetters.ContainsKey(columnName))
{
// read value of given property
var value = propertyGetters[columnName](this);
// run validation
var results = new List<ValidationResult>();
var vc = new ValidationContext(this, null, null) { MemberName = columnName };
Validator.TryValidateProperty(value, vc, results);
// transpose results
var errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
return string.Join(Environment.NewLine, errors);
}
return string.Empty;
}
private static Dictionary<string, Func<object, object>> GetPropertyGetterLookups(Type objType)
{
var key = objType.FullName ?? "";
if (!PropertyLookupCache.ContainsKey(key))
{
var o = objType.GetProperties()
.Where(p => GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, CreatePropertyGetter);
PropertyLookupCache[key] = o;
return o;
}
return (Dictionary<string, Func<object, object>>)PropertyLookupCache[key];
}
private static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
{
var instanceParameter = System.Linq.Expressions.Expression.Parameter(typeof(object), "instance");
var expression = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(
System.Linq.Expressions.Expression.ConvertChecked(
System.Linq.Expressions.Expression.MakeMemberAccess(
System.Linq.Expressions.Expression.ConvertChecked(instanceParameter, propertyInfo.DeclaringType),
propertyInfo),
typeof(object)),
instanceParameter);
var compiledExpression = expression.Compile();
return compiledExpression;
}
private static ValidationAttribute[] GetValidations(PropertyInfo property)
{
return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}
Okay, this brings me to the issue. The thing is the validation works perfectly, but lets say I have a property (within my view model called: Person) with a StringLength attribute. The StringLength attribute fires as soon as the application is opened. The user didn't even have a chance to do anything. The validation fires as soon as the application is started.
public class PersonViewModel : BaseViewModel<BaseProxyWrapper<PosServiceClient>>
{
private string _password = string.Empty;
[StringLength(10, MinimumLength = 3, ErrorMessage = "Password must be between 3 and 10 characters long")]
public string Password
{
get { return this._password; }
set
{
if (this._password != value)
{
this._password = value;
this.OnPropertyChanged("Password");
}
}
}
}
I have noticed that this is caused by the IDataErrorInfo.this[string columnName] property, and in turn it calls the ValidateProperty method. But, I have no idea how to fix this?
There could be two issues...
Do you populate yopur Person instance by using the public properties?
e.g.
new Person { Password = null }
This will fire the property changed notification for Password and will validate it.
Some developers also set the properties in constructors...
public class Person {
public Person() {
this.Password = null;
}
}
Recommended practise is to use private fields...
public class Person {
public Person() {
_password = null;
}
public Person(string pwd) {
_password = pwd;
}
}
OR
You can create a flag in our view model base say IsLoaded. Make sure you set it to true only after your UI is loaded (probably in UI.Loaded event). In your IDataErrorInfo.this[string columnName] check if this property is true and only then validate the values. Otherwise return null.
[EDIT]
The following change did the job:
public class PersonViewModel : BaseViewModel<BaseProxyWrapper<PosServiceClient>>
{
private string _password;
[StringLength(10, MinimumLength = 3, ErrorMessage = "Password must be between 3 and 10 characters long")]
public string Password
{
get { return this._password; }
set
{
if (this._password != value)
{
this._password = value;
this.OnPropertyChanged("Password");
}
}
}
public PersonViewModel(BaseProxyWrapper<PosServiceClient> model)
: base(model)
{
this._username = null;
}
}
Something I've done in the past is change the update source trigger to explicit, create a behavior that will update the source when the TextBox loses focus, and then attach that behavior to the TextBox.
I am developing a WPF application which is bind to a DataGrid as given below:
<DataGrid AutoGenerateColumns="True"
ItemsSource="{Binding Source={StaticResource tradeViewModel},
Path=Result}"
Where the Path pointing to Result property in view model which inturn calls TradePriceChanger.GetTrades()
The TradePriceChanger.GetTrades method return sList of Trade as given below:
public static class TradePriceChanger
{
static List<Trade> tradeList = new List<Trade>();
static int TradeCounter = 1;
static ManualResetEvent manualReset = new ManualResetEvent(false);
public static IEnumerable<Trade> GetTrades()
{
Thread t1 = new Thread(new ThreadStart(One));
t1.Start();
Thread t2 = new Thread(new ThreadStart(Two));
t2.Start();
manualReset.WaitOne();
return tradeList;
}
public static void One()
{
for (int i = 1; i < 10; i++)
{
tradeList.Add(new Trade ( "One" + i, i ));
}
Interlocked.Decrement(ref TradeCounter);
if (TradeCounter == 0)
manualReset.Set();
}
public static void Two()
{
for (int i = 1; i < 10; i++)
{
tradeList.Add(new Trade("Two" + i, i));
}
Interlocked.Decrement(ref TradeCounter);
if (TradeCounter == 0)
manualReset.Set();
}
}
And the Trade class has a timer which updates its price to a random value after every 1sec.
public class Trade:INotifyPropertyChanged
{
private string _name;
private double _price;
public Trade(string name, double price)
{
_name = name;
_price = price;
TimerCallback callback = new TimerCallback(ChangePrice);
Timer timer = new Timer(callback, this, 0, 1000);
}
private static void ChangePrice(object obj)
{
if (obj is Trade)
{
Trade trade = obj as Trade;
trade.Price = trade.Price + ( new Random().Next(-1,2) * (new Random().NextDouble()));
}
}
public string Name { get { return _name; } set { _name = value; } }
public double Price
{
get
{
return _price;
}
set
{
_price = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Price"));
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Now the problem is the values in the grid are refreshing properly When I run only thread t1 in the TradePriceChanger.GetTrades(). But, If I run both, no refresh is happening on UI.
Am I missing something here.
Please let me know.
Thanks,
Mahesh
It seems you're only waiting for one thread by using 'manualReset.WaitOne();', but if you want both threads to have the time to execute, you should use WaitAll(); Else one thread could finish and the function would exit as the wait would have been satisfied.
It also seems you're adding to your tradelist without any thread synchronization, so both threads can write to this list and cause inconsistent state in your list