Is it possible to bind to a property of a property?
Here is what I have:
[Bindable(true)]
public class DataClass
{
private string DescriptionValue = null;
private Content DataContent Value = new Content();
....
[Bindable(true)]
public Content DataContent
{
get { return DataContent; }
set { DataContent = value; }
}
[Bindable(true)]
public string Description
{
get { return DescriptionValue; }
set { DescriptionValue = value; }
}
...
}
[Bindable(true)]
public class Content
{
private object ContentValue = null;
private Color StateBackColorValue;
...
[Bindable(true)]
public object Content
{
get { return ContentValue; }
set { ContentValue = value; }
}
[Bindable(true)]
public Color StateBackColor
{
get { return StateBackColorValue; }
set { StateBackColorValue = value; }
}
...
}
Is it somehow possible to bind a control to DataContent.Content or any other property of the Content class? I know that I could introduce properties in DataContent class that map the Content class properties. I just wanted to know if hierarchical databinding with properties is possible.
What type of data-binding are you doing?
With simple binding (TextBox.Text to a single object, for example), yes, you can use "Foo.Bar.SomeProp" as the member. For PropertyGrid, you can mark the objects with [TypeConverter(typeof(ExpandableObjectConverter))] and it will work.
The tricky one is list binding (DataGridView etc); here, no: it doesn't flatten easily. You can do it if you go to great lengths (ITypedList etc), but it really isn't worth it - just add shim properties to the parent:
public string ChildName {
get {return child == null ? "" : child.Name;} // and setter if you want
}
Related
I used this SO Question to retrieve a property of an object using reflection. The property I retrieved is another object that has a property called Value that I need to access. All of the potential objects that I retrieve using reflection derive from the same class EntityField and therefore all have a Value property. I saw this SO question that hinted at how I might be able to access the Value property, but I couldn't quite put together the correct code. How can I access the Value property on an object retrieved by reflection?
My Attempts
var parent = entity.GetType().GetProperty("Property");
parent.GetType().GetProperty("Value").SetValue(parent, newValue); // parent.GetType() is null
(parent as EntityField<T>).Value = newValue; // Not sure how to dynamically set T since it could be any system type
Main (Original Code)
private static void SetValues(JObject obj, EntityBase entity)
{
// entity.GetType().GetProperty("Property") returns an EntityField Object
// I need to set EntityField.Value = obj["Value"]
// Current code sets EntityField = obj["Value"] which throws an error
entity.GetType().GetProperty("Property").SetValue(entity, obj["Value"], null);
}
EntityField
public class EntityField<T> : EntityFieldBase
{
private Field _Field;
private T _Value;
public EntityField(Field field, T value){
this._Field = field;
this._Value = value;
}
public Field Field
{
get
{
return this._Field;
}
set
{
if (this._Field != value)
{
this._Field = value;
}
}
}
public T Value
{
get
{
return this._Value;
}
set
{
if (!EqualityComparer<T>.Default.Equals(this._Value, value))
{
this._Value = value;
this._IsDirty = true;
}
}
}
}
Try this:
entity.GetType().GetProperty("Value").SetValue(entity, obj["Value"], null);
You need to specify the name of the property in the GetProperty() method. I suspect there was no such property called 'Property' :)
Edit: After reading your comments try
entity.Property.GetType().GetProperty("Value").SetValue(entity, obj["Value"], null);
Tried the following in LinqPad and it worked...
class TestChild<T>
{
public T ChildProperty { get; set; }
}
class TestParent<T>
{
public TestChild<T> ParentProperty { get; set; }
}
void Main()
{
var instance = new TestParent<string>
{
ParentProperty = new TestChild<string>()
};
instance.GetType()
.GetProperty("ParentProperty")
.GetValue(instance)
.GetType()
.GetProperty("ChildProperty")
.SetValue(instance.ParentProperty, "Value");
Console.WriteLine(instance.ParentProperty.ChildProperty);
}
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 am working on small winforms app. One of my forms contains few comboBoxes:
As I am trying to use MVP pattern in my project, so I decided to create View and Presenter for that form. The communicate via adequate interface.
ComboBox can be fully decribed (for my needs) with its DataSource (i.e. list os strings) and SelectedIndex. That' s why I created proper interface:
public interface IMyView
{
MyViewPresenter { set; }
IEnumerable<string> ComboBox1stDataSource { get; set; }
int ComboBox1SelectedIndex { get; set; }
IEnumerable<string> ComboBox2ndDataSource { get; set; }
int ComboBox2ndSelectedIndex { get; set; }
//for third comboBox it will be the same
}
I implemented that interface in my View class:
public partial class MaterialDatabasePropertiesForm : Form, IMaterialDatabasePropertiesView, IMyView
{
public MaterialDatabasePropertiesPresenter Presenter { private get; set; }
public IEnumerable<string> ComboBox1stDataSource
{
get { return comboBox1st.DataSource as List<string>; }
set { comboBox1st.DataSource = value; }
}
public int ComboBox1SelectedIndex
{
get { return comboBox1st.SelectedIndex; }
set { comboBox1st.SelectedIndex = value; }
}
public IEnumerable<string> ComboBox2ndDataSource
{
get { return comboBox2nd.DataSource as List<string>; }
set { comboBox2nd.DataSource = value; }
}
public int ComboBox2ndSelectedIndex
{
get { return comboBox2nd.SelectedIndex; }
set { comboBox2nd.SelectedIndex = value; }
}
}
When everything is set like above I use properties declared in Interface in my Presenter to change properties of comboBoxes in form.
Although it may seem like a good solution it isn' t enough for me. In my origial application I have 14 comboBoxes, and that number may change in future.
What I am trying to is making it more elastic. I was thinking about creatin some collection of comboBoxes in view, but I can' t figure it out.
My sample solutin is bad, as it doesn' t even compile:
private List<List<string>> collectionOfComboBoxesDataSources = new List<List<string>>()
{
ref comboBox1st.DataSource, // I get error:
ref comboBox2nd.DataSource, // "Cannot acces non-static field
ref comboBox3rd.DataSource // <comboBoxName> in static context"
};
//this property would be part of IMyView
public List<List<string>> CollectionOfComboBoxesDataSources
{
get { return collectionOfComboBoxesDataSources; }
set { collectionOfComboBoxesDataSources = value; }
}
What can I do to create collection (or something working similar) to acces my comboBoxes properties?
You could try to iterate through your form.
List<ComboBox> listOfCombobox = new List<ComboBox>();
foreach(var combobox in this.controls.OfType<ComboBox>())
{
listOfCombobox.Add(combobox);
}
If you're trying to do this.
Then you can access the list via index, so you can access your properties of each combobox.
Update the model from the view model
I have read some post about the MVVM but I not sure if understand the
way that the view model is updating the model
Currently I have two text boxes in the UI which is bound to the XAML view and call to the view model when the event was raised .
when should be the place in the view model when I updating the model?
This is the view model
class ViewModel:INotifyPropertyChanged
{
private String _url;
private String _TemplateType;
public string URL
{
get { return _url; }
set
{
if (value != _url)
{
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return _TemplateType; }
set
{
if (value != _TemplateType)
{
_TemplateType= value;
OnPropertyChanged("URL");
}
}
}
The model
internal class DefineAddinModel
{
public string TemplateType { get; set; }
public String URL { get; set; }
}
The ViewModel usually acts as a wrapper around the Model and contains a reference to the Model which is can update either in response to commands or automatically in property setters.
UPDATE:
Here's an example of having the VM act as a wrapper around the Model. This may seem useless in your example but you will find in many cases the VM's getters/setters need to do some sort of transformation on the values rather than simply passing them through.
class ViewModel:INotifyPropertyChanged
{
private DefineAddinModel model;
public string URL
{
get { return model.URL; }
set
{
if (value != model.URL)
{
model.url = value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return model.TemplateType; }
set
{
if (value != model.TemplateType)
{
model.TemplateType = value;
OnPropertyChanged("TemplateType");
}
}
}
The better way to update your Model Is by using an event, its safer, so choose weather using a button click or lost focus, or whatever you want
void button_click(object sender,eventsarg e)
{
MyObj.URL = App.Locator.MyVM.MyDefineAddinModel.URL;// App.Locator because MVVMLight is tagged
MyObj.TemplateType = App.Locator.MyVM.MyDefineAddinModel.TemplateType ;
}
but personnaly i Use the following steps :
1- In your ViewModel create a CurrentItem object of type DefineAddinModel and without OnPropertyChanged then bind it to the View(UI) DataContext of the RootElement on the View )
2- for the model I use the INotifyPropertyChanged for each propery
3- after binding the datacontext of your root element to the CurrentItem of your ViewModel then bind just URL and TemplateType properties to your Controls, so any thing changes on the textbox will update CurrentItem properties
you can also chose the type of the binding (On LostFocus, or OnPropertyChanged)
You need to bind your TextBoxes to the two properties URL and TemplateType.
Try to use Commands (in the ViewModel)instead of events (in The CodeBehind) since you are in MVVM.
For updating the model : use a button with it's Command property bound to OnSave just like this example:
private String _url;
private String _TemplateType;
private DefineAddinModel _defineAddin;
public DefineAddinModel DefineAddin
{
get {return _defineAddin;}
set
{
_defineAddin = value;
OnPropertyChanged("DefineAddin");
}
}
public string URL
{
get { return _url; }
set
{
if (value != _url)
{
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return _TemplateType; }
set
{
if (value != _TemplateType)
{
_TemplateType= value;
OnPropertyChanged("URL");
}
}
}
public RelayCommand OnSaved
{
get;
set;
}
public ViewModel()
{
DefineAddin = new DefineAddinModel();
OnSaved = new RelayCommand(()=>
{
DefineAddin.URL = URL ;
DefineAddin.TemplateType = TemplateType;
});
Think about using third parties like MVVMLight it helps you a lot with MVVM and the helpers around it (Commands, Messenger, ViewModelLocator ...)
I think that the correct answer here is 'it depends'.
In most general cases, the advantage of actually using a ViewModel is also to track 'transient state', i.e. the state of an 'edit in progress' operation.
In this particular case, you would not push your changes directly to the Model every time a value is updated, instead you would do this via an 'Update' ICommand implementation that will collect all the data from the ViewModel and push it down to the Model.
This approach gives you many advantages:
The user of the view can change their mind as many times as they want, and only when they are happy will the Model actually get updated with their definitive choices
It greatly reduces the load on your persistence service, since only final changes are pushed through.
It allows you to do final validation on a complete set of values, rather than transient states, and hence reduces programming complexity and overhead.
It also makes your UI far more fluid since all the examples above are pushing updates on the UI Dispatcher, and avoids you having to cater for this via Tasks or other async approaches.
The backing model is never in an inconsistent state, since I would imagine that all values on one View/ViewModel are related, and only make sense when updated together using an ACID approach.
Here's an example of how I'd do it.
public class ViewModel:INotifyPropertyChanged {
private String _url;
private String _TemplateType;
public ViewModel(){
UpdateCommand = new DelegateCommand(OnExecuteUpdate, OnCanExecuteUpdate);
}
public bool OnCanExecuteUpdate(object param){
// insert logic here to return true when one can update
// or false when data is incomplete
}
public void OnExecuteUpdate(object param){
// insert logic here to update your model using data from the view model
}
public ICommand UpdateCommand { get; set;}
public string URL{
get { return _url; }
set {
if (value != _url) {
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType {
get { return _TemplateType; }
set {
if (value != _TemplateType) {
_TemplateType= value;
OnPropertyChanged("TemplateType");
}
}
}
... etc.
}
public class DelegateCommand : ICommand {
Func<object, bool> canExecute;
Action<object> executeAction;
public DelegateCommand(Action<object> executeAction)
: this(executeAction, null) {}
public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute) {
if (executeAction == null) {
throw new ArgumentNullException("executeAction");
}
this.executeAction = executeAction;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter) {
bool result = true;
Func<object, bool> canExecuteHandler = this.canExecute;
if (canExecuteHandler != null) {
result = canExecuteHandler(parameter);
}
return result;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged() {
EventHandler handler = this.CanExecuteChanged;
if (handler != null) {
handler(this, new EventArgs());
}
}
public void Execute(object parameter) {
this.executeAction(parameter);
}
}
I have class a that keeps track of video streams, and for simplicity I group like properties in a sub classes using auto properties to access them. I then bound the whole class to an BindingList, but only the None Nested Properties show up. How can i get the Nested Properties to show up also?
public class Stream: : INotifyPropertyChanged
{
public bool InUse {
get { return _inUse; }
set {
_inUse = value;
OnPropertyChanged("InUse");
}
}
}
....
internal SubCodec Codec { get; set; }
internal class SubCodec
{
public string VideoCodec
{
get { return _audioCodec; }
set {
_audioCodec = value;
OnPropertyChanged("AudioCodec");
}
}
....
}
You need to fire OnPropertyChanged of the parent type, not on the child type.
public class Stream : INotifyPropertyChanged
{
private SubCodec _codec;
internal SubCodec Codec
{
get
{
return _codec;
}
set
{
_codec = value;
//note that you'll have problems if this code is set to other parents,
//or is removed from this object and then modified
_codec.Parent = this;
}
}
internal class SubCodec
{
internal Stream Parent { get; set; }
private string _audioCodec;
public string VideoCodec
{
get { return _audioCodec; }
set
{
_audioCodec = value;
Parent.OnPropertyChanged("VideoCodec");
}
}
}
}
It may be simpler to put the Stream in the constructor of SubCodec and not allow it to be changed. It would be one way of avoiding the problems I mention in the comment of the Codec set method.
You need to raise PropertyChanged event on SubCodec
private SubCoded _codec;
internal SubCodec Codec
{
get {return _codec;}
set
{
_codec = value;
OnPropertyChanged("Codec");
}
}