I bound (at least i think i did) data to a ListBox following a tutorial. The class element I want bound has data in it but i see nothing on the ListBox after some event. I have the following partial XAML:
<ListBox x:Name="jukeBoxListBox" Height="227" VerticalAlignment="Top" ItemsSource="{Binding FilePathList}"/>
In the WPF form cs file I have. Should i set to class FolderItems or its attr filePathList? Also should I use ObservableCollection instead of list?
InitializeComponent();
FolderItems folderItems = new FolderItems();
this.DataContext = folderItems.FilePathList;
My data class:
class FolderItems : INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged implementation
private ObservableCollection<String> _pathList = new ObservableCollection<string>();
public ObservableCollection<String> FilePathList
{
get { return _pathList; }
set
{
if (value != _pathList)
{
_pathList = value;
Notify("FilePathList");
}
}
}
}
I think I need to mention that I change the List elements in a Button click event. Maybe the below is a part of the problem.
//in the event fItems is an instance of FolderItems
var files = new ObservableCollection<string>();
ProcessFiles(of.SelectedPath, files);
fItems.FilePathList = files;
//...
private void ProcessFiles(string path, ICollection<string> files)
{
foreach (var file in Directory.GetFiles(path).Where(name => name.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase)))
files.Add(file);
foreach (var directory in Directory.GetDirectories(path))
ProcessFiles(directory, files);
}
I hail from Javaland and am brand new to C#. Please excuse my language.
If you'd change List<string> to ObservableCollection<string> (see here), your binding would get notified about changes in the list, e.g. when you add items.
Also, you must change the property name in the Notify call to filePathList.
And you should follow coding conventions for properties in .Net, which are usually written with a leading uppercase character. So your property would be FilePathList.
private ObservableCollection<String> pathList = new ObservableCollection<string>();
public ObservableCollection<String> FilePathList
{
get { return pathList; }
set
{
if (value != pathList)
{
pathList = value;
Notify("FilePathList"); // changed here
}
}
}
Change the binding to the renamed property:
<ListBox ... ItemsSource="{Binding FilePathList}"/>
See also Binding to Collections and Using Collection Objects as a Binding Source.
UPDATE
Your ProcessFiles method should be written as shown below to enable recursion.
private void ProcessFiles(string path, ICollection<string> files)
{
foreach (var file in Directory.GetFiles(path).Where(name => name.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase)))
{
files.Add(file);
}
foreach (var directory in Directory.GetDirectories(path))
{
ProcessFiles(directory, files);
}
}
And be called like this:
var files = new ObservableCollection<string>();
ProcessFiles(of.SelectedPath, files);
var folderItems = new FolderItems();
folderItems.FilePathList = files;
DataContext = folderItems;
Or if you need to access the FolderItems object later (perhaps in some event handler) you might get it back from the DataContext:
DataContext = new FolderItems();
...
var folderItems = DataContext as FolderItems;
ProcessFiles(of.SelectedPath, folderItems.FilePathList);
try this.
I search for dll files insteed of mp3. you can change the pattern as you need.
public class FolderItems : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//Temp Data
public FolderItems()
{
//Add System.windows.Form assampbly.
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
FilePathList = ProcessFiles(dialog.SelectedPath);
////var directories = new System.IO.DirectoryInfo("C:\\Windows\\").GetFiles().Select(x => x.Name);
//foreach (var file in directories)
//{
// FilePathList.Add(file);
//}
}
}
private ObservableCollection<String> ProcessFiles(string path)
{
string[] directories;
ObservableCollection<String> fileList = new ObservableCollection<string>();
var files = new System.IO.DirectoryInfo(path).GetFiles("*.dll").Select(x => x.Name); //Directory.GetFiles(path).Where(name => name.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase));
fileList = new ObservableCollection<String>(files.ToList<String>());
//your processing further
//directories = Directory.GetDirectories(path);
//foreach (string directory in directories)
//{
// // Process each directory recursively
// ProcessFiles(directory);
//}
return fileList;
}
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<String> _pathList = new ObservableCollection<string>();
public ObservableCollection<String> FilePathList
{
get { return _pathList; }
set
{
if (value != _pathList)
{
_pathList = value;
Notify("FilePathList");
}
}
}
}
Related
I have a ListView in XAML that is bound to an ObservableCollection in the ViewModel. Upon initialization or OnAppearing() the ListView items are displayed perfectly.
However, when I try to update the ListView items from within the page (through ViewModel) the items are updated but the old items are still there.
Basically, the new items are added to the ListView but below the items that were in the ObservableCollection before. I have implemented INotifyPropertyChanged and I think I have done everything correct (although clearly not).
Please tell me what I'm doing wrong. I've tried Clear() on the Collection but to no avail (same outcome).
BaseViewModel:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
}
XAML:
<ListView IsEnabled="{Binding IsLoadingTable, Converter={Helpers:InverseBoolConverter}}"
IsVisible="{Binding IsLoadingTable, Converter={Helpers:InverseBoolConverter}}"
ItemsSource="{Binding LeagueStandings}"
SelectedItem="{Binding SelectedTeam, Mode=TwoWay}"
ItemSelected="ListView_ItemSelected"
RowHeight="60"
SeparatorVisibility="Default"
SeparatorColor="{DynamicResource accentColor}">
Page.cs:
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.LoadLeagueStandingsCommand.Execute(_divisionId);
ViewModel.LoadPickerItemsCommand.Execute(null);
}
ViewModel Initialization:
private ObservableCollection<Standing> _leagueStandings;
public ObservableCollection<Standing> LeagueStandings
{
get { return _leagueStandings ?? (_leagueStandings = new ObservableCollection<Standing>()); }
set { SetValue(ref _leagueStandings, value); }
}
ViewModel Methods:
private async Task LoadLeagueStandings(string divId)
{
if (_hasLoadedStandings)
return;
if (IsLoadingTable)
return;
_hasLoadedStandings = true;
_divisionId = divId;
try
{
IsLoadingTable = true;
await _pageService.DisplayAlert("loading Selected", _divisionId, "ok");
var v = await GetLeagueTableAsync(_htmlParser, _divisionId);
LeagueStandings = new ObservableCollection<Standing>(v);
}
catch (Exception)
{
System.Diagnostics.Debug.WriteLine("Exception caught in DivisionsViewModel.cs.(LoadLeagueStandings).");
}
finally
{
IsLoadingTable = false;
}
}
ViewModel method called when Picker item changes:
private async Task SelectItem(string item)
{
if (item == null)
return;
SelectedItem = null;
var id = await _divisionFinder.GetDivisionIdAsync(item);
var v = await GetLeagueTableAsync(_htmlParser, id);
LeagueStandings = new ObservableCollection<Standing>(v);
}
Edit* - Picture of outcome. First collection ends at number 5 and new collections appends to end of listview starting at 1 again.
ListView Image
Edit 2:
public async Task<IEnumerable<Standing>> GetLeagueTableAsync(string divisionId)
{
// todo: get division Id from picker
string address = "";
if (IsOnline)
{
if (divisionId != "")
address = $"{BaseUrls.LeagueStandings}{divisionId}";
try
{
var config = Configuration.Default.WithDefaultLoader();
var document = await BrowsingContext.New(config).OpenAsync(address);
var cSelector = "table[class='table table-striped table-hover table-bordered'] tr";
var table = document.QuerySelectorAll(cSelector).Skip(1);
int count = 0;
foreach (var c in table)
{
var cells = c.QuerySelectorAll("td").ToArray();
_leagueStandings.Add(new Standing(++count, cells[0].TextContent.Trim(), cells[1].TextContent.Trim(),
cells[2].TextContent.Trim(), cells[3].TextContent.Trim(),
cells[4].TextContent.Trim(), cells[5].TextContent.Trim(),
cells[6].TextContent.Trim(), cells[7].TextContent.Trim()));
}
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine($"\n\n Exception caught LoadingLeagueTable - {e} \n\n");
}
}
return _leagueStandings;
Since you're not adding neither removing items, and you're replacing the reference you need to raise the event telling that the view has changed. Instead of your code, replace it by this
private ObservableCollection<Standing> _leagueStandings;
public ObservableCollection<Standing> LeagueStandings
{
get { return _leagueStandings; }
set {
_leagueStandings = value;
RaisePropertyChanged("LeagueStandings");
}
}
Also for future references, ObservableCollection already implements INotifyPropertyChanged so you don't need to SetValue(x)..
I had some problems with serialization of my classes implementing INotifyPropertyChanged events. (Binary Serialization of ViewModel (ObservableCollection))
Solved as recommended in SerializationException when serializing instance of a class which implements INotifyPropertyChanged
Now I have a problem with the deserialization. When doing it like
BinaryFormatter formatter = new BinaryFormatter();
FileStream fs_open = new FileStream(#"C:\Users\vm_user\Documents\Visual Studio 2015\testbin.txt", FileMode.Open);
ViewModels.MainViewModel mvm1 = (ViewModels.MainViewModel)formatter.Deserialize(fs_open);
fs_open.Close();
The deserialized objects do not fire any events anymore.
What do I have to do to get the same functionality as before the serialization? Right now I do it with this:
BinaryFormatter formatter = new BinaryFormatter();
ViewModels.MainViewModel mvm1 = new ViewModels.MainViewModel();
FileStream fs_open = new FileStream(#"C:\Users\vm_user\Documents\Visual Studio 2015\testbin.txt", FileMode.Open);
foreach (ViewModels.WatchedFile file in ( (ViewModels.MainViewModel)formatter.Deserialize(fs_open) ).WatchedFiles)
{
ViewModels.WatchedFile wf = new ViewModels.WatchedFile(file.Name, file.Path, file.Tags, new ObservableCollection<ViewModels.WatchedFile>());
foreach (ViewModels.WatchedFile subs in file.Subs)
{
wf.Subs.Add(refeshLoadedFiles(subs));
}
mvm1.WatchedFiles.Add(wf);
}
fs_open.Close();
and refreshLoadedFiles:
private ViewModels.WatchedFile refeshLoadedFiles( ViewModels.WatchedFile fileSource)
{
ViewModels.WatchedFile wf = new ViewModels.WatchedFile(fileSource.Name, fileSource.Path, fileSource.Tags, new ObservableCollection<ViewModels.WatchedFile>());
foreach (ViewModels.WatchedFile subs in fileSource.Subs)
{
wf.Subs.Add(refeshLoadedFiles(subs));
}
return wf;
}
But this canĀ“t be the goal of serialization, can it? Beacause if I do it this way I can write my information into any textfile with simple text stating the type and then all the rest...
Thanks for your help.
This is the ViewModel (edit):
namespace WatchedFile.ViewModels
{
[Serializable()]
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
[Serializable()]
public class WatchedFile : ViewModelBase
{
#region Name Property
private String _name = default(String);
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged();
}
}
}
#endregion Name Property
#region Path Property
private String _path = default(String);
public String Path
{
get { return _path; }
set
{
setDisplayImage(value);
if (value != _path)
{
_path = value;
OnPropertyChanged();
}
}
}
#endregion Path Property
#region Tags Property
private ObservableCollection<Tag> _tags = new ObservableCollection<Tag>();
public ObservableCollection<Tag> Tags
{
get { return _tags; }
protected set
{
if (value != _tags)
{
_tags = value;
OnPropertyChanged();
}
}
}
#endregion Tags Property
#region Subs Property
private ObservableCollection<WatchedFile> _subs = new ObservableCollection<WatchedFile>();
public ObservableCollection<WatchedFile> Subs
{
get { return _subs; }
protected set
{
setDisplayImage(Path);
if (value != _subs)
{
_subs = value;
_subs.CollectionChanged += _subs_CollectionChanged;
OnPropertyChanged();
}
}
}
private void _subs_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Subs = Sort(Subs);
}
#endregion Subs Property
[NonSerialized()]
private BitmapImage _displayImage = default(BitmapImage);
public BitmapImage DisplayImage
{
get { return _displayImage; }
protected set
{
if (value != _displayImage)
{
_displayImage = value;
OnPropertyChanged();
}
}
}
public WatchedFile(): this(string.Empty,string.Empty,new ObservableCollection<Tag>(),new ObservableCollection<WatchedFile>())
{
}
public WatchedFile(String name, String path, ObservableCollection<Tag> tags, ObservableCollection<WatchedFile> subitems)
{
Subs = WatchedFile.Sort(subitems);
Name = name;
Path = path;
Tags = tags;
}
public static ObservableCollection<WatchedFile> Sort(ObservableCollection<WatchedFile> files)
{
if (files == null)
return files;
ObservableCollection<WatchedFile> filesReturn = new ObservableCollection<ViewModels.WatchedFile>();
WatchedFile[] sortedArray = files.ToArray();
WatchedFile temp;
for (int j = 1; j <= sortedArray.Length - 1; j++)
{
for (int i = j; i > 0; i--)
{
if (sortedArray[i].Subs != null && sortedArray[i].Subs.Count > 1)
{
ObservableCollection<WatchedFile> subs = Sort(sortedArray[i].Subs);
sortedArray[i].Subs.Clear();
foreach (WatchedFile f in subs)
sortedArray[i].Subs.Add(f);
}
if (sortedArray[i - 1].Subs != null && sortedArray[i - 1].Subs.Count > 1)
{
ObservableCollection<WatchedFile> subs = Sort(sortedArray[i - 1].Subs);
sortedArray[i - 1].Subs.Clear();
foreach (WatchedFile f in subs)
sortedArray[i - 1].Subs.Add(f);
}
if (( sortedArray[i].Name ).CompareTo(sortedArray[i - 1].Name) == -1)
{
temp = sortedArray[i];
sortedArray[i] = sortedArray[i - 1];
sortedArray[i - 1] = temp;
}
else
break;
}
}
filesReturn.Clear();
foreach (WatchedFile f in sortedArray)
filesReturn.Add(f);
return filesReturn;
}
}
[Serializable()]
public class Tag
{
public Tag(String value)
{
Value = value;
}
public String Value { get; private set; }
}
[Serializable()]
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
}
#region WatchedFiles Property
private ObservableCollection<WatchedFile> _watchedFiles = new ObservableCollection<WatchedFile>();
public ObservableCollection<WatchedFile> WatchedFiles
{
get { return _watchedFiles; }
protected set
{
if (value != _watchedFiles)
{
_watchedFiles =WatchedFile.Sort(value);// value;
_watchedFiles.CollectionChanged += _watchedFiles_CollectionChanged;
OnPropertyChanged();
}
}
}
private void _watchedFiles_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
WatchedFiles = WatchedFile.Sort(WatchedFiles);
}
#endregion WatchedFiles Property
}
}
They don't fire because nothing is subscribed to them. In WPF, the framework will subscribe during binding in the background when the view or control is loaded. Since you're doing this at a different stage in the pipeline, you're going to have to figure out a better way to load the model at the right stage so that the subscriptions occur again. Hard to say without seeing more, but this is very smelly. Is this a control or the full view's datacontext?
At the very least, force a rebind of the model. Otherwise, maybe serialize a dto instead and inject into the viewmodel and use it to restore state before loading the view, or give the main view model a method that takes the dto and rebinds it to an observable property. Like I said, it's hard to say without seeing what you're really doing.
How would I be able to change the data in my ObservableCollection without adding or using add to my collection?
var items = await service.GetTrucksAsync();
foreach (var item in items)
{
MyCollection.Add(new TruckItems
{
TruckId = item.TruckId,
TruckQuoteId = item.QuoteId,
TruckPhaseId = item.CurrentPhaseId,
TruckChassisManufacturer = item.ChassisManufacturer,
TruckChassisModel = item.ChassisModel,
TruckStatus = item.Status,
TruckJobNumber = item.JobNumbers,
TruckAddedBy = item.AddedBy,
TruckClientName = item.ClientName,
TruckClientSurname = item.ClientSurname,
TruckClientDetail = item.ClientDetail,
TruckCurrentPhase = item.CurrentPhase
});
}
dgViewProjects.ItemsSource = MyCollection;
I do not want to clear the collection and the add data again, as it causes my datagrid's UI to 'flicker' (clear and load new data again). I need it to be smooth as hell. :)
EDIT: INotifyPropertyChanged is implemented in my class
public class TruckItems : INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You TruckItems class needs to implement the INotifyPropertyChanged interface.
For example:
public class TruckItems : INotifyPropertyChanged
{
private int _truckQuoteId;
public int TruckQuoteId
{
get { return _truckQuoteId; }
set
{
if(value != _truckQuoteId)
{
value = _truckQuoteId;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TruckQuoteId));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
If you're unable to alter the TruckItems class (DAL), you need to create a new class and copy the properties. (for example using a Mapper)
update:
The problem you are facing is, that you're creating a new instance of TruckItems.
var items = await service.GetTrucksAsync();
foreach (var item in items)
{
var truckItem = MyCollection.FirstOrDefault(i => i.TruckId == item.TruckId);
bool isNew = false;
if(truckItem == null)
{
truckItem = new TruckItems();
isNew = true;
}
truckItem.TruckId = item.TruckId;
truckItem.TruckQuoteId = item.QuoteId;
truckItem.TruckPhaseId = item.CurrentPhaseId;
truckItem.TruckChassisManufacturer = item.ChassisManufacturer;
truckItem.TruckChassisModel = item.ChassisModel;
truckItem.TruckStatus = item.Status;
truckItem.TruckJobNumber = item.JobNumbers;
truckItem.TruckAddedBy = item.AddedBy;
truckItem.TruckClientName = item.ClientName;
truckItem.TruckClientSurname = item.ClientSurname;
truckItem.TruckClientDetail = item.ClientDetail;
truckItem.TruckCurrentPhase = item.CurrentPhase;
if(isNew )
MyCollection.Add(truckItem);
}
I am trying to create a SwitchCell with a list of Elements.
Eventhough I found out how to do that with a plain string-List thanks to stackoverflow I can't find out what I'm doing wrong when I try to bind the Cell-Properties to a self-made struct.
This is my code:
public class RestaurantFilter
{
public List<FilterElement> Types;
public RestaurantFilter(List<string> types)
{
Types = new List<FilterElement>();
foreach (string type in types)
Types.Add(new FilterElement { Name = type, Enabled = false });
}
}
public struct FilterElement
{
public string Name;
public bool Enabled;
}
public FilterPage()
{
List<string> l = new List<string>(new string[] { "greek", "italian", "bavarian" });
RestaurantFilter filter = new RestaurantFilter(l);
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(() =>
{
var cell = new SwitchCell();
cell.SetBinding(SwitchCell.TextProperty, "Name");
cell.SetBinding(SwitchCell.IsEnabledProperty, "Enabled");
return cell;
});
types.ItemsSource = filter.Types;
Content = types;
}
But the SwitchCell's in the Application do not show the Name or the Boolean.
About the IsEnabledProperty - there seem to be a knonw bug with the IsEnabled property that will be fixed in the Xamarin.Forms 2.3.0-pre1 release so that might be related to your case:
https://bugzilla.xamarin.com/show_bug.cgi?id=25662
About the Name property - try changing your FilterElement struct to a class with properties and PropertyChangedEventHandler like this and it will work:
public class FilterElement
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
private bool _enabled;
public bool Enabled
{
get { return _enabled; }
set
{
_enabled = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Enabled"));
}
}
}
}
That way you will be able to update the Types list and it will automatically update the ListView.
By the way, if you want to turn the filter on or off based on your ViewModels (not enable or disable it), you need to use the OnProperty for the binding:
https://developer.xamarin.com/api/field/Xamarin.Forms.SwitchCell.OnProperty/
Say I have a BindingList<Person> where Person has a public string property called Name. Is there a way to effectively (if not directly) bind to the Current property (which is a Person object) and then index into the Name property?
I'm imagining a binding set up like
nameLabel.DataBindings.Add(
new Binding("Text", this.myBindingListSource, "Current.Name", true));
or
nameLabel.DataBindings.Add(
new Binding("Text", this.myBindingListSource.Current, "Name", true));
Both of these approaches produce runtime errors.
Currently, I am simply subscribing to the BindingList's CurrentChanged event and handling updates in there. This works but I would prefer the DataBinding approach if possible.
Using the NestedBindingProxy class provided below, you can do
nameLabel.DataBindings.Add(
new Binding("Text", new NestedBindingProxy(this.myBindingListSource, "Current.Name"), true));
Below is the c# code for NestedBindingProxy. The problem with WinForms data binding is it doesn't detect value changes when you use a navigation path that contains multiple properties. WPF does this though. So I created the class NestedBindingProxy that does the change detection and it exposes a property called "Value" that the windows binding can bind too. Whenever any property changes in the navigation path, the notify property changed event will fire for the "Value" property.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
namespace WindowsFormsApplication4
{
public sealed class NestedBindingProxy : INotifyPropertyChanged
{
class PropertyChangeListener
{
private readonly PropertyDescriptor _prop;
private readonly WeakReference _prevOb = new WeakReference(null);
public event EventHandler ValueChanged;
public PropertyChangeListener(PropertyDescriptor property)
{
_prop = property;
}
public object GetValue(object obj)
{
return _prop.GetValue(obj);
}
public void SubscribeToValueChange(object obj)
{
if (_prop.SupportsChangeEvents)
{
_prop.AddValueChanged(obj, ValueChanged);
_prevOb.Target = obj;
}
}
public void UnsubsctribeToValueChange()
{
var prevObj = _prevOb.Target;
if (prevObj != null)
{
_prop.RemoveValueChanged(prevObj, ValueChanged);
_prevOb.Target = null;
}
}
}
private readonly object _source;
private PropertyChangedEventHandler _subscribers;
private readonly List<PropertyChangeListener> _properties = new List<PropertyChangeListener>();
private readonly SynchronizationContext _synchContext;
public event PropertyChangedEventHandler PropertyChanged
{
add
{
bool hadSubscribers = _subscribers != null;
_subscribers += value;
bool hasSubscribers = _subscribers != null;
if (!hadSubscribers && hasSubscribers)
{
ListenToPropertyChanges(true);
}
}
remove
{
bool hadSubscribers = _subscribers != null;
_subscribers -= value;
bool hasSubscribers = _subscribers != null;
if (hadSubscribers && !hasSubscribers)
{
ListenToPropertyChanges(false);
}
}
}
public NestedBindingProxy(object source, string nestedPropertyPath)
{
_synchContext = SynchronizationContext.Current;
_source = source;
var propNames = nestedPropertyPath.Split('.');
Type type = source.GetType();
foreach (var propName in propNames)
{
var prop = TypeDescriptor.GetProperties(type)[propName];
var propChangeListener = new PropertyChangeListener(prop);
_properties.Add(propChangeListener);
propChangeListener.ValueChanged += (sender, e) => OnNestedPropertyChanged(propChangeListener);
type = prop.PropertyType;
}
}
public object Value
{
get
{
object value = _source;
foreach (var prop in _properties)
{
value = prop.GetValue(value);
if (value == null)
{
return null;
}
}
return value;
}
}
private void ListenToPropertyChanges(bool subscribe)
{
if (subscribe)
{
object value = _source;
foreach (var prop in _properties)
{
prop.SubscribeToValueChange(value);
value = prop.GetValue(value);
if (value == null)
{
return;
}
}
}
else
{
foreach (var prop in _properties)
{
prop.UnsubsctribeToValueChange();
}
}
}
private void OnNestedPropertyChanged(PropertyChangeListener changedProperty)
{
ListenToPropertyChanges(false);
ListenToPropertyChanges(true);
var subscribers = _subscribers;
if (subscribers != null)
{
if (_synchContext != SynchronizationContext.Current)
{
_synchContext.Post(delegate { subscribers(this, new PropertyChangedEventArgs("Value")); }, null);
}
else
{
subscribers(this, new PropertyChangedEventArgs("Value"));
}
}
}
}
}
Try this:
Binding bind = new Binding("Text", myBindingListSource, "Current");
bind.Format += (s,e) => {
e.Value = e.Value == null ? "" : ((Person)e.Value).Name;
};
nameLabel.DataBindings.Add(bind);
I haven't tested it but it should work and I've been waiting for the feedback from you.
I stumbled across this by accident (four years after the original post), and after a quick read, I find that the accepted answer appears to be really over-engineered in respect to the OP's question.
I use this approach, and have posted an answer here to (hopefully) help anyone else who has the same problem.
The following should work (if infact this.myBindingListSource implements IBindingList)
I would just create a binding source to wrap my binding list (in my Form/View)
BindingSource bindingSource = new BindingSource(this.myBindingListSource, string.Empty);
And then just bind to the binding source like this:
nameLabel.DataBindings.Add(new Binding("Text", bindingSource, "Name"));
I think the OP's original code didn't work because there is no member called 'Current' on a BindingList (unless the OP has some sort of specialised type not mentioned).