WPF UserControl Binding to Collection - c#

I have a listBox on my UI, and a backing collection in my viewmodel.
I have been trying to databind/datatemplate it so that if a new item is added in the collection, a new user control is added to the UI. However, I cannot get this to work FULLY.
To clarify, I can get the right number of controls to appear on the UI for the number of items in the collection, but cannot then bind the properties of the user control to the item in the collection.
This is only me trying to figure it out initially to then make this vastly more complex later on, perhaps good that I have this issue now..
DataBinding WPF Code:
<ListBox x:Name="SubSystemList" ItemsSource="{Binding Path=SubSystems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type src:SubSystem}">
<controls:SubSystem DeviceCount="{Binding Path=DeviceCount}" SystemName="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Properties from the usercontrol that i am trying to render into:
public string SystemName
{
get { return (string) GetValue(SubSystenNameProperty); }
set { SetValue(SubSystenNameProperty, value); }
}
public int DeviceCount
{
get { return (int) GetValue(DeviceCountProperty); }
set { SetValue(DeviceCountProperty, value); }
}
I am reluctant to post much more code (straight away..) as it would only become more difficult to follow.
The Debug WriteLine's in the setters do not fire at all (breakpoints dont get hit). Which makes me think its a path/binding issue.
Any help would be much appreciated.
EDIT
src:SubSystem code: (removed additional properties for clarity)
public class SubSystem
{
[XmlAttribute("Name")]
[DataMember]
public string Name
{
get { return _name; }
set { _name = value; }
}
[DataMember]
public int DeviceCount
{
get { return _deviceCount; }
set { _deviceCount = value; }
}
}
DependencyProperties on usercontrols:subsystem
public static readonly DependencyProperty DeviceCountProperty = DependencyProperty.Register(
nameof(DeviceCount), typeof(int), typeof(SubSystem));
public static readonly DependencyProperty SubSystenNameProperty = DependencyProperty.Register(
nameof(SystemName), typeof(string), typeof(SubSystem));
Another Edit
Further head scratching revealed this in the output logs:
em.Windows.Data Error: 40 : BindingExpression path error: 'SystemName' property not found on 'object' ''SubSystem' (HashCode=32985660)'. BindingExpression:Path=SystemName; DataItem='SubSystem' (HashCode=32985660); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
Makes me think theres something happening in the user control code behind/bindings that it doesnt like, amusing thing is, after i was getting confused, i changed the name of the usercontrol to subsystemcontrol, and made sure all the binding names in the uc were correct. so not sure why its doing this entirely.. further digging on going.

You seem to have your dependency property declaration wrong.
As example, the full declaration of a string SystemName dependency property would look like this:
public static readonly DependencyProperty SystemNameProperty =
DependencyProperty.Register("SystemName", typeof(string), typeof(SubSystem));
public string SystemName
{
get { return (string)GetValue(SystemNameProperty); }
set { SetValue(SystemNameProperty, value); }
}
Note that there shouldn't be anything else then GetValue/SetValue in the property wrapper. If you need to get notified about property value changes, you can register a PropertyChangedCallback via property metadata with another overload of the Register method.

I think that you should:
Implement InotifyPropertyChanged interface in your SubSystem class.
SetValue and GetValue of the DependencyProperties SystemName and DeviceCount
public class SubSystem:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[XmlAttribute("Name")]
[DataMember]
public string Name
{
get { return _name; }
set {
_name = value;
OnPropertyChanged("Name");
}
}
[DataMember]
public int DeviceCount
{
get { return _deviceCount; }
set {
_deviceCount = value;
OnPropertyChanged("DeviceCount");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}

In your SubSystem class Name property should be replaced by SystemName
[XmlAttribute("SystemName")]
[DataMember]
public string SystemName
{
get { return _name; }
set { _name = value; }
}

Related

Binding doesn't work - No property, bindable property, or event found error

Am having an issue with binding, but I first searched with several questions on this, but no luck, below is the error am getting :
Error: Position 18:36. No property, bindable property, or event found
for 'Lat', or mismatching type between value and property
Below is my xaml file :
<controls:MapView x:Name="map" VerticalOptions="FillAndExpand">
<controls:MapView.Center>
<controls:Position Lat="{Binding latitude}" Long="{Binding longitude}" />
</controls:MapView.Center>
</controls:MapView>
Then the c# code is as below :
public partial class DisplayMap : ContentPage
{
private double latitude { get; }
private double longitude { get; }
public DisplayMap()
{
InitializeComponent();
this.latitude = 0.3476;
this.longitude = 32.5825;
BindingContext = this;
}
What am I missing ?
The issue seems to be a lack of publicly-accessible bindable properties in the Position class (notice that the error mentions Lat which is a member of Position). Position should look something like this:
public class Position : BindableObject
{
public static readonly BindableProperty LatProperty = BindableProperty.Create(nameof(Lat), typeof(double), typeof(Position), 0);
public double Lat
{
get { return (double)this.GetValue(LatProperty); }
set { this.SetValue(LatProperty, value); }
}
public static readonly BindableProperty LongProperty = BindableProperty.Create(nameof(Long), typeof(double), typeof(Position), 0);
public double Long
{
get { return (double)this.GetValue(LongProperty); }
set { this.SetValue(LongProperty, value); }
}
// ...
I suggest you take a look at the official documentation for Bindable Properties. Essentially, the error message you are getting is because it is looking for LatProperty when trying to bind using the accessor Lat.
The reason why you are not able to use the Lat and Long property is that if you check the Position class there is no Bindable Property defined for them in it which means you cannot access them in XAML,
A possible solution is to download the Sample project and take the code and make the respective changes for it to have Bindable Properties.
For that, you can check #Aaron's answer

Two Way binding to a Dependency Property in a User Control and call a method

I know, title is a little confusing so let me explain. I have a user control that has a dependency property. I access this dependency property with a regular property called Input. In my view model I also have a property called Input. I have these two properties bound together in XAML using two-way binding as shown below:
<uc:rdtDisplay x:Name="rdtDisplay" Input="{Binding Input, Mode=TwoWay}" Line1="{Binding myRdt.Line1}" Line2="{Binding myRdt.Line2}" Height="175" Width="99" Canvas.Left="627" Canvas.Top="10"/>
Okay in my view model, I call a method whenever the value of Input is changed as shown in my property:
public string Input
{
get
{
return input;
}
set
{
input = value;
InputChanged();
}
}
The problem with this is that when I set the value of Input in my view model it only updates the value of the variable input as per my setter in my property. How can I get this to update back to the dependency property in the user control? If I leave the code input = value; out then I get a compilation error.
I need something like this:
public string Input
{
get
{
return UserControl.Input;
}
set
{
UserControl.Input = value;
InputChanged();
}
}
If I make the Input property in my view model look like this:
public string Input
{
get; set;
}
then it works, however, I am unable to call the InputChanged() method that I need to call when the Property is changed. All suggestions are appreciated.
Implement INotifyPropertyChanged in your ViewModel
public class Sample : INotifyPropertyChanged
{
private string input = string.Empty;
public string Input
{
get
{
return input;
}
set
{
input = value;
NotifyPropertyChanged("Input");
InputChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
In your case, you can do it in the code behind of your usercontrol

Binding from ViewModel to behaviour

I have a property on my ViewModel that I need bound to a BindableProperty in my behaviour but I cant seem to get it to bind.
private int _test;
public int Test {
get {
return _test;
}
set {
if (SetProperty (ref _test, value)) {
_profileIsDirty = true;
NotifyPropertyChanged ("AllowUpdate");
}
}
}
Here is the property in the behaviour
public static readonly BindableProperty MinLengthProperty = BindableProperty.Create("MinLength", typeof(int), typeof(MinLengthValidator), 0);
public int MinLength
{
get { return (int)GetValue(MinLengthProperty); }
set { SetValue(MinLengthProperty, value); }
}
This is the property on the ViewModel and this is how I am trying to bind to it in XAML
<behave:TelephoneNumberValidatorBehaviour x:Name="phoneValidator" MinLength="{Binding Test, Mode=OneWay}"/>
But it never binds. Am I doing something wrong here?
Have you tried to change the value of your NotifyPropertyChanged to "Test" instead of "AllowUpdate"? Otherwise the UI is never notified that the value has changed of Test. Instead it raises that the value the AllowUpdate property has changed, which does not exist in your ViewModel.
The third parameter of BindableProperty.Create should be type of the class where the BindableProperty is defined. Based on your sample, I guess it should be typeof(TelephoneNumberValidatorBehaviour) and not typeof(MinLengthValidator)

INotifyPropertyChanged Implemented now what?

I am trying to create a list box that displays a set of data that will be updated over time. I have a simple list box:
<ListBox Name="lbRegisters" ItemsSource="{Binding}" />
And I have defined a class for my objects:
public class register : INotifyPropertyChanged
{
private int address;
public int Address { get { return address; } }
private int value;
public int Value
{
get{ return value; }
set{
this.value = value;
OnValueChanged("Value");
}
}
public register(int a)
{
address = a;
value = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnValueChanged(string name){
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public override string ToString()
{
return address + ": " + value;
}
}
And I hold a list of these in an ObservableCollection<register>. Then set the ListBox.ItemsSource=registerslist; in the CodeBehind. The list is initialized and the inital data displays correctly.
Now what do I need to do to get my ListBox to update when a "register.Value" changes. The event handler is called but there nothing is subscribed to the event.
I guess I need to trigger something in the ListBox or ObservableCollection to tell the GUI to update. I have read dozens of posts of a similar problem but they all seem to indicate that once you have implemented INotofyPropertyChanged it just automagicaly works.
What is the next step?
The problem is on your ToString() function. Yes it could be used to display complex string in ListView items but it is not a proper way to bindings because ListView does not knows when part of this string was changed.
Do the following:
1. Declare property on register class like
public string AddressValue
{
get { return address + ": " + value; }
}
2. Add OnValueChanged("AddressValue") in value and address setters like:
public int Value
{
get{ return value; }
set{
this.value = value;
OnValueChanged("Value");
OnValueChanged("AddressValue")
}
}
3. Declare you ListBox with ItemTemplate like:
<ListBox x:Name="lbRegisters" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding AddressValue}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So the idea is adding new property to register class which will be updated when address and value changed. And bind list item text to this property.
I think understand the spirit of what you're asking (even though it's obviously unfinished ATM). I'd recommend using something that inherits from DependencyObject and using dependency properties. Use the propdp snippet in Visual Studio. It will save you a ton of boilerplate code and wiring.

Data Binding to an object in C#

Objective-c/cocoa offers a form of binding where a control's properties (ie text in a textbox) can be bound to the property of an object. I am trying to duplicate this functionality in C# w/ .Net 3.5.
I have created the following very simple class in the file MyClass.cs:
class MyClass
{
private string myName;
public string MyName
{
get
{
return myName;
}
set
{
myName = value;
}
}
public MyClass()
{
myName = "Allen";
}
}
I also created a simple form with 1 textbox and 1 button. I init'd one instance of Myclass inside the form code and built the project. Using the DataSource Wizard in Vs2008, I selected to create a data source based on object, and selected the MyClass assembly. This created a datasource entity. I changed the databinding of the textbox to this datasource; however, the expected result (that the textbox's contents would be "allen") was not achieved. Further, putting text into the textbox is not updating the name property of the object.
I know i'm missing something fundamental here. At some point i should have to tie my instance of the MyClass class that i initialized inside the form code to the textbox, but that hasn't occurred. Everything i've looked at online seems to gloss over using DataBinding with an object (or i'm missing the mark entirely), so any help is great appreciated.
Edit:
Utilizing what I learned by the answers, I looked at the code generated by Visual Studio, it had the following:
this.myClassBindingSource.DataSource = typeof(BindingTest.MyClass);
if I comment that out and substitute:
this.myClassBindingSource.DataSource = new MyClass();
I get the expected behavior. Why is the default code generated by VS like it is? Assuming this is more correct than the method that works, how should I modify my code to work within the bounds of what VS generated?
You must assign the textbox's data source to be your new datasource. But additionally, you must assign the datasource's datasource to be an instance of your class.
MyDataSource.DataSource = new MyClass();
TextBox1.DataSource = MyDataSource;
That should work for your first pass. As others have mentioned, you may need to implement additional interfaces on your class (INotifyPropertyChanged etc), if you are going to be modifying the class properties via any background processes.
If you are only updating the properties via the form, then you do not need this step.
You should implement the INotifyPropertyChanged interface to your MyClass type:
public class MyClass : INotifyPropertyChanged
{
private string _myName;
public string MyName
{
get { return _myName; }
set
{
if( _myName != value )
{
_myName = value;
OnPropertyChanged("MyName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if( PropertyChanged != null )
PropertyChanged( this , new PropertyChangedEventArgs(propertyName) );
}
}
This interface is required for the DataBinding infrastructure if you want to support simple databinding.
The INotifyPropertyChanged interface is used to notify a 'binding' that a property has changed, so the DataBinding infrastructure can act accordingly to it.
Then, you can databind the MyName property to the Text Property of the textbox.
I get an error message in the DataBinding.Add("TEXT", myObject, myObjectProperty) method
This is probably because you're missing the explicit {get;set;} on the property declaration!
BAD:
public string FirstName; //<-- you will not be able to bind to this property!
GOOD:
public string FirstName { get; set; }
Looks like you probably need a Bindable attribute on your MyName property (and follow Frederik's suggestion as well):
[System.ComponentModel.Bindable(true)]
public string MyName
{
get { return _myName; }
set
{
if( _myName != value )
{
_myName = value;
OnPropertyChanged("MyName");
}
}
}
Via: http://support.microsoft.com/kb/327413
I don't have any code in front of me, but I think the data source is kind of like a collection. You have to add an instance of MyClass to the data source, and that's what the form fields will bind to. There's also methods for navigating through the data source to multiple instances of MyClass, but it doesn't sound like you need that. Check the docs for DataSource.
I don't think you need to implement any fancy interfaces. I seem to remember there's a method on the data source that lets you refresh or rebind the current item after you change some values.
using System.Collections.Generic;
public class SiteDataItem
{
private string _text;
private string _url;
private int _id;
private int _parentId;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
}
}
public string Url
{
get
{
return _url;
}
set
{
_url = value;
}
}
public int ID
{
get
{
return _id;
}
set
{
_id = value;
}
}
public int ParentID
{
get
{
return _parentId;
}
set
{
_parentId = value;
}
}
public SiteDataItem(int id, int parentId, string text, string url)
{
_id = id;
_parentId = parentId;
_text = text;
_url = url;
}
public static List<SiteDataItem> GetSiteData()
{
List<SiteDataItem> siteData = new List<SiteDataItem>();
siteData.Add(new SiteDataItem(1, 0, "All Sites", ""));
siteData.Add(new SiteDataItem(2, 1, "Search Engines", ""));
siteData.Add(new SiteDataItem(3, 1, "News Sites", ""));
siteData.Add(new SiteDataItem(4, 2, "Yahoo", "http://www.yahoo.com"));
siteData.Add(new SiteDataItem(5, 2, "MSN", "http://www.msn.com"));
siteData.Add(new SiteDataItem(6, 2, "Google", "http://www.google.com"));
siteData.Add(new SiteDataItem(7, 3, "CNN", "http://www.cnn.com"));
siteData.Add(new SiteDataItem(8, 3, "BBC", "http://www.bbc.co.uk"));
siteData.Add(new SiteDataItem(9, 3, "FOX", "http://www.foxnews.com"));
return siteData;
}
}
More detail you can read tutorial dapfor. com

Categories