In C#, I have a suffiently complex Model. I already have a WPF Client to manipulate that model. I'm using MVVM. All objects in that model support INotifyPropertyChanged and all properties that are collections support INotifyCollectionChanged.
Take this as a simplied example:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace CollectionTest1
{
public class PropertyChangedSupport : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChange([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Company : PropertyChangedSupport
{
private string name;
public String Name { get { return name; } set { name = value; FirePropertyChange(); } }
public ObservableCollection<Employee> Employees { get; } = new ObservableCollection<Employee>();
}
public class Employee : PropertyChangedSupport
{
private string name;
public String Name { get { return name; } set { name = value; FirePropertyChange(); } }
public ObservableCollection<PresentTimespan> PresentTimespans { get; } = new ObservableCollection<PresentTimespan>();
public Boolean IsPresentAt(DateTime t)
{
foreach (PresentTimespan pt in PresentTimespans)
{
if (pt.Start.CompareTo(t) <= 0 && pt.Finish.CompareTo(t) >= 0) return true;
}
return false;
}
}
public class PresentTimespan : PropertyChangedSupport
{
private string comment;
public String Comment { get { return comment; } set { comment = value; FirePropertyChange(); } }
private DateTime start;
public DateTime Start { get { return start; } set { start = value; FirePropertyChange(); } }
private DateTime finish;
public DateTime Finish { get { return finish; } set { finish = value; FirePropertyChange(); } }
}
public class CompanyStatusView : PropertyChangedSupport
{
private DateTime currentTime;
public DateTime CurrentTime { get { return currentTime; } set { currentTime = value; FirePropertyChange(); } }
private Company currentCompany;
public Company CurrentCompany { get { return currentCompany; } set { currentCompany = value; FirePropertyChange(); } }
public ObservableCollection<Employee> PresentEmployees { get; } = new ObservableCollection<Employee>();
public CompanyStatusView()
{
UpdatePresentEmployees();
}
private void UpdatePresentEmployees()
{
PresentEmployees.Clear();
foreach (Employee e in CurrentCompany.Employees) {
if (e.IsPresentAt(currentTime)) PresentEmployees.Add(e);
}
}
}
}
I'd like to have UpdatePresentEmployees called whenever there are changes in:
Collection Company.Employees.PresentTimespans
Property Company.Employees.PresentTimespans.Start
Property Company.Employees.PresentTimespans.Finish
Collection Company.Employees
Property CurrentTime
Property CurrentCompany
So it's basically any property or collection read by UpdatePresentEmployees.
My best solution so far included registering a lot of event handlers to all the objects mentioned above. That included to have a couple of Dictionary instances to track which added objects I have to subscribe to and especially which I have to unsubscribe from.
The most difficult and annoying part was to subscribe to all the PresentTimespan objects to listen for property changes and all the PresentTimespans collections of Employee to listen for collection changes.
My guess is that there has to be a better way to do this.
After all, in JFace (Java) there is a very interesting solution that uses ObservableTracker. So there you'd only provide the code for UpdatePresentEmployees and ObservableTracker tracks which objects have been read and automatically makes you listen for changes in any of these and also correctly unsubscribes from irrelevant objects. So there are better approaches to this problem in general. What is C# offering? Can it do better than my best solution I mentioned above? Can I avoid some of the boilerplate code? Can it be done with .net provided classes or do I need some additional classes/libraries?
Thanks for your kind help and advice in advance!
You could use BindingList instead of ObservableCollection and attach to the the ListChanged Event. But keep in mind that BindingList has some disadvantages like not being very fast. For further information this could be interesting: difference between ObservableCollection and BindingList
If you dont wanna use BindingList you have to wire your items with events.
As pointed out by Nikhil Agrawal, Rx or ReactiveUI is a good framework for my purpose. So I consider that to be a solution.
I can see this question has been asked before but nothing seems to work for me.
I have a wpf desktop app.
i have this comboBox:
<ComboBox ItemsSource="{Binding Users, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Value.Login"
SelectedValue="{Binding SelectedManagerUser,
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
IsSynchronizedWithCurrentItem="True" />
The data source is a dictionary object:
public Dictionary<string,UserRecord> Users
{
get
{
//get data
}
set { _Users = value; RaisePropertyChanged(Constants.VM_Users); }
}
I add a new entry in my MVVM and update the data.
I then set the selected item in my mvvm:
private UserRecord _selectedManagerUser;
public UserRecord SelectedManagerUser
{
get
{
return _selectedManagerUser;
}
set
{
_selectedManagerUser = value;
RaisePropertyChanged("SelectedManagerUser");
}
}
SelectedManagerUser = Users[temp];
public class UserRecord : ViewModelBase
{
private int _Active;
private int _UserRecordId;
private string _UserRef;
private string _FName;
private string _SName;
private string _Login;
private string _Salt;
private int _IsAdmin;
private string _FullName;
private string _Branch;
private string _Position;
private string _Department;
public int Disabled { get { return _Active; } set { _Active = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Active); } }
public int UserRecordId { get { return _UserRecordId; } set { _UserRecordId = value; RaisePropertyChanged("UserRecordId"); } }
public string UserRef { get { return _UserRef; } set { _UserRef = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_UserRef); } }
public string FName { get { return _FName; } set { _FName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_FName); } }
public string SName { get { return _SName; } set { _SName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_SName); } }
public string Login { get { return _Login; } set { _Login = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Login); } }
public string Salt { get { return _Salt; } set { _Salt = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Salt); } }
public int IsAdmin { get { return _IsAdmin; } set { _IsAdmin = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_IsAdmin); } }
public string Branch { get { return _Branch; } set { _Branch = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Branch); } }
public string Position { get { return _Position; } set { _Position = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Position); } }
public string Department { get { return _Department; } set { _Department = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Department); } }
public string FullName { get { return FName + ", " + SName; } set { _FullName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Fullname); } }
}
I know the new item has been added because -
I can see it int the dropdown
I set a breakpoint in my code and inspect.
The combo box just displays an empty value.
Anything else I can try?
thanks
Not sure what's going wrong on your side, but it might be helpful to look at a working solution.
XAML:
<ComboBox ItemsSource="{Binding Users}"
DisplayMemberPath="Value.Name"
SelectedValue="{Binding SelectedUser}"
SelectedValuePath="Value" />
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += WindowLoaded;
var vm = new ViewModel();
vm.Users.Add("u1", new UserRecord { Name = "User 1" });
vm.Users.Add("u2", new UserRecord { Name = "User 2" });
vm.Users.Add("u3", new UserRecord { Name = "User 3" });
DataContext = vm;
}
private void WindowLoaded(object sender, RoutedEventArgs e)
{
// make sure it works after DataContext was set
var vm = (ViewModel)DataContext;
vm.SelectedUser = vm.Users["u2"];
}
}
public class UserRecord
{
public string Name { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Dictionary<string, UserRecord> Users { get; }
= new Dictionary<string, UserRecord>();
private UserRecord selectedUser;
public UserRecord SelectedUser
{
get { return selectedUser; }
set
{
selectedUser = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedUser)));
}
}
}
Your SelectedManagerUser property should be changed to this. The SelectedManagerUser property is set with new value but you do not raise that event so the UI will not be updated.
private UserRecord _selectedManagerUser;
public UserRecord SelectedManagerUser
{
get
{
return _selectedManagerUser;
}
set
{
_selectedManagerUser = value;
RaisePropertyChanged("SelectedManagerUser");
}
}
Download Prism from Nuget and inherit your class from BindableBase.
After it use this:
private UserRecord selectedManagerUser;
public UserRecord SelectedManagerUser
{
get { return this.selectedManagerUser; }
set { this.SetProperty(ref this.selectedManagerUser, value); }
}
One of two things can cause this. First, it might be because you're not setting SelectedManagerUser to be an instance of UserRecord that is NOT in the Dictionary, or that Dictionaries still suck for databinding. Lemme cover them both.
When you work with ItemsSource and SelectedItem bindings, if you want SelectedItem changes to be reflected in the UI, you must set it to an instance that can be found within ItemsSource. The control, by default, will look for the item in the source that is referentially equal to the selected item. I'm 99% sure it will use IEquatable<T> instead of reference checking if your items implement it.
If that's not your problem, then it's because Dictionaries suck for databinding.
Dictionaries are TERRIBLE for databinding. Just awful. If you need a keyed collection and you want to bind against it, create a custom collection extending KeyedCollection. With some extra work TItem can implement INPC (make the key read only, tho) and the collection can implement INCC. Works great for binding. Why do I mention this? Read on...
Your problem is that, within the ComboBox, SelectedItem is actually of type KeyValuePair<string,UserRecord>, and NOT UserRecord. So the binding will NOT work. If you grab a copy of Snoop and examine the bindings at runtime, you'll see this.
The problem is that the control doesn't know jack squat about Dictionaries. All it knows is IEnumerable<T>. Dictionary<K,T> implements IEnumerable<KeyValuePair<K,T>>, so the control creates an item for each key value pair. SelectedItem is also a key value pair. So, when you bind that to a property of type UserRecord, yes it is able to use the SelectedValuePath to set the value properly, but it cannot (does not) [ninja edit: unless this behavior has changed over the past few years :/] iterate the enumerable in order to find the correct key value pair when you set the value in your view model.
If UserRecord's key value is a property within the type, then definitely create a KeyedCollection for it. KeyedCollection<Tkey,TItem> implements 'IEnumerable` so it works seamlessly with bindings. If not, wrap it in a proxy, or add the property.
And when I say "wrap it in a proxy", anybody who says "what, like a KeyValuePair?" I'm going to punch you through the internet. The proxy becomes the value you bind against. Don't waste your time with this SelectedValuePath nonsense. Work with the proxies directly. When you need your value, extract it at the last moment, not immediately after the binding executes.
I'm trying to implement a PATCH on Web API for an object that will be stored in a DB. The input object from the controller has all of the properties that can be modified but we allow the client to choose which fields to send back. We only want to update the MongoDB representation if some of the fields have changed or been set. We started using a Dirty object pattern (not sure this is a pattern) whereby when you set a property you also record that it is dirty. for instance
public class Example
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
TitleWasSet = true;
}
}
public bool TitleWasSet {get;set;}
}
This could work but is kind of tedious and I feel it exposes lots of logic that could be contained.
So a solution I came up with was to store the update Actions in the inbound object then reapply them to the Mongo Object in a Try Update fashion.
like this:
public class Data
{
public string Header { get; set; }
public int Rating { get; set; }
}
public class EditDataRequest
{
private readonly List<Action<Data>> _updates;
public EditDataRequest()
{
_updates = new List<Action<Data>>();
}
public string Header
{
set
{
_updates.Add(data => {data.Header = value;});
}
}
public int Rating
{
set
{
_updates.Add(data => {data.Rating = value;});
}
}
public bool TryUpdateFromMe(Data original)
{
if (_updates.Count == 0)
return false;
foreach (var update in _updates)
{
update.Invoke(original);
}
return true;
}
}
Now this would work great but it doesn't take account of the values being the same. So i then looked at changing the list of actions to a list of functions that would return a bool if there was a difference in the value.
private readonly List<Func<Data, bool>> _updates;
And then the properties would look like this:
public int Rating
{
set
{
_updates.Add(data => {
if (data.Rating != value)
{
data.Rating = value;
return true;
}
return false;
});
}
}
And the try update method...
public bool TryUpdateFromMe(Data original)
{
if (_updates.Count == 0)
return false;
bool changesRequired = false;
foreach (var update in _updates)
{
changesRequired |= update.Invoke(original);
}
return changesRequired;
}
As you can see that property set implementation is rather clunky and would make the code nasty to read.
I'd like a way of extracting the check this property value then update it to another method that I can reuse in each property - I assume this is possibly somehow but it might not be.
Of course, if you have better suggestions for how to handle the PATCH situation then I'd be happy to hear them as well.
Thanks for reading this far.
my c# class is
public class Otp
{
public String Time;
public Otp()
{
}
public void setTime(String Time)
{
this.Time = Time;
}
public String getTime()
{
return this.Time;
}
}
my problem is when i bind a list of object to grid view it give me error how can i solve this problem?
The databinding only can bind against properties and your Time is defined as a member variable. To make Time a property, define it with getter and setter:
public String Time { get; set; }
And there is no need in additional get and set methods.
public String Time
{
get
{
//your code here
return this.Time;//ex.
}
set
{
//your code here
this.Time = Time;//ex.
}
}
EDIT: Question Reconstructed.
OK, I have revisited my get and set methods, but I am still very unclear on how it all works.
What I want to achieve is the Model is populated by the Controller, from the values that it takes form the form. This is then sent to the Db_Facade, which compares the uName and uPwd, and if they are equal returns the ACCESS, which will be set for the entire scope of the program.
I don't know if the get and set declarations are done correctly, or if they can be bunched together (If this is possible it would be great because I will be using this for much larger collections of data), and I'm pretty sure I'm implementing them wrong as well.
If you can help, my knowledge of Accessors is incredibly limited.
Here is my Compare Login method in my Controller:
public static void Compare_Login(User_Login_View Login_View)
{
User_Model getACCESS = new User_Model(); // Creates a new oject of User_Model
getACCESS.Name = Login_View.txtUsername.Text; //Populates the Model from the Login View
getACCESS.Pwd = Login_View.txtPassword.Text;
if (getACCESS.ACCESSLEVEL > 0)
{
Login_View.Close();
}
else
{
Login_View.lblError.Visible = true;
}
Login_View.Menu.SetMenuView();
}
Here is my Model:
public class User_Model
{
public string Name
{
get
{
return Db_Facade.uName;
}
set
{
Db_Facade.uName = value;
}
}
public string Pwd
{
get
{
return Db_Facade.uPwd;
}
set
{
Db_Facade.uPwd = value;
}
}
public int ACCESSLEVEL
{
get
{
return Db_Facade.ACCESS;
}
set
{
Db_Facade.ACCESS = value;
}
}
}
Here is the dummy database comparison:
class Db_Facade
{
public static string uName;
public static string uPwd;
public static string cPwd;
public static int ACCESS;
public static void getLoginACCESS()
{
uName = "paul";
uPwd = "pwd";
ACCESS = 1;
/* I get a "getACCESS does not exist" error here
if (uName == getACCESS.Name && uPwd == getACCESS.Pwd)
{
getACCESS.ACCESSLEVEL = ACCESS;
}
else
{
getACCESS.ACCESSLEVEL = 0;
}
*/
}
}
I don't know if it's needed, but here is my View
public partial class User_Login_View : Form
{
public Menu_View Menu { get; set; }
public User_Login_View()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, EventArgs e)
{
User_Controller.Compare_Login(this);
}
}
2 Questions / Hints
1.) Where do you call your getLoginACCESS() ?
2.) Why do you think Db_Facade is able to access getACCESSfrom your class User_Controller?
a solution would be to modyfie your getLoginACCESS() to getLoginACCESS(User_Model getACCESS) and than call it in your Compare_Login(User_Login_View Login_View) befor your if like Db_Facade.etLoginACCESS(getACCESS);