Databinding an ArrayList to a ListBox in VB.NET? - vb.net

I'm working in VB.NET
I have an ArrayList named Invoices populated with objects of the class Invoice.
I'd like to data bind this to a ListBox so that as the contents of the ArrayList are updated and changed the ListBox updates. I've implemented a .ToString function on the Invoice class, I just don't know how I'd go about binding the ArrayList to the ListBox.
Any suggestions?

I'm going to make the assumption that this is winforms.
If you want two-way data-binding, you need a few things:
to detect addition/removal etc, you need a data-source that implements IBindingList; for classes, BindingList<T> is the obvious choice (ArrayList simply won't do...)
to detect changes to properties of the objects, you need to implement INotifyPropertyChanged (normally you can use the "*Changed" pattern, but this isn't respected by BindingList<T>)
Fortunately, ListBox handles both of these. A full example follows; I've used C#, but the concepts are identical...
using System;
using System.ComponentModel;
using System.Windows.Forms;
class Data : INotifyPropertyChanged{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this,
new PropertyChangedEventArgs(propertyName));
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Button btn1, btn2;
BindingList<Data> list = new BindingList<Data> {
new Data { Name = "Fred"},
new Data { Name = "Barney"},
};
using (Form frm = new Form
{
Controls =
{
new ListBox { DataSource = list, DisplayMember = "Name",
Dock = DockStyle.Fill},
(btn1 = new Button { Text = "add", Dock = DockStyle.Bottom}),
(btn2 = new Button { Text = "edit", Dock = DockStyle.Bottom}),
}
})
{
btn1.Click += delegate { list.Add(new Data { Name = "Betty" }); };
btn2.Click += delegate { list[0].Name = "Wilma"; };
Application.Run(frm);
}
}
}

Related

Bind to an Item of a Dependency Collection

I'm trying to create a custom control that has a header and a footer and body. The idea is that the body of the report is a custom stack panel control that will allow the user to indicate page orientation and grouping. I created a dependency property on the custom UC to accept an IList of the custom stack panel. What I am trying to do is bind to one of the stack panels in the list. But for some reason the binding is not working.
The ReportPage:
public class ReportPage : StackPanel
{
//Nothing right now but will eventually include controls for page orientation and size (8.5x11, 11x17, etc.)
}
The UserControl code behind:
public partial class Report : UserControl, INotifyPropertyChanged
{
public Report()
{
ReportPages = new List<ReportPage>();
}
public static readonly DependencyProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
ActivePage = value[0];
OnPropertyChanged(nameof(ActivePage));
}
}
private ReportPage _activePage;
public ReportPage ActivePage
{
get => _activePage;
set
{
_activePage = value;
OnPropertyChanged(nameof(ActivePage));
}
{
}
The UserControl xaml:
<Grid>
<!--Some xaml for the header and footer.-->
<ContentControl Content="{Binding ActivePage, RelativeSource={RelativeSource, FindAncestor, AncestorType=local:Report}}"/>
</Grid>
Here is how I am consuming the custom control. This should, in my mind at least, make three "pages" which I can toggle between using a button control that I didn't share.
<reportEngine:Report>
<reportEngine:Report.ReportPages>
<reportEngine:ReportPage>
<TextBlock>This is Page 1</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 2</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 3</TextBlock>
</reportEngine:ReportPage>
</reportEngine:Report.ReportPages>
</reportEngine:Report>
Any Ideas why the binding isn't working?
So I at least found a quick work around. I utilized the Collection Changed Event handler pattern from this answer and modified it for static dependency properties. Then, to get the values from the collection bound to the dependency property I create a static instance of the Report object in the constructor and use that to pass various values back to the object from the collection. Something like this:
public partial class Report : UserControl, INotifyPropertyChanged
{
private static Report _thisReport;
public Report()
{
InitializeComponent();
ReportPages = new ObservableCollection<ReportPage>();
_thisReport = this;
}
public static readonly DependencyProperty ReportPagesProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report), new FrameworkPropertyMetadata(ReportPagesChanged));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
//Update some other properties associated with the control (Total Page Numbers, etc.)
}
}
private static void ReportPagesChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
var newColl = (INotifyCollectionChanged)eventArgs.NewValue;
if (newColl != null)
newColl.CollectionChanged += ReportPages_CollectionChanged;
var oldColl = (INotifyCollectionChanged)eventArgs.OldValue;
if (oldColl != null)
oldColl.CollectionChanged -= ReportPages_CollectionChanged;
}
private static void ReportPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs eventArgs)
{
var newPages = (IList<ReportPage>) sender;
//Updates properties of the Report control.
_thisReport.ActivePage = newPages[0];
_thisReport.TotalPageNumber = newPages.Count;
}
}
Whether this is "correct" or not I couldn't say, but it works. If someone has a better answer I will change the answer.

Reference error in an expandable listview

I am trying to create an expandable listview in forms and I am receiving an error "The type or namespace name 'Items' could not be found (are you missing a using directive or an assembly reference?)" I was trying to have the buttons hide or show with a boolean value upon touch.
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ExpandableListViewTest
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListViewType : ContentPage
{
public ObservableCollection<string> Items { get; set; }
public bool IsVisible { get; set; }
public ListViewType()
{
InitializeComponent();
IsVisible = false;
Items = new ObservableCollection<string>
{
new Items
{
Title = "Sad"
},
new Items
{
Title = "Angry"
},
new Items
{
Title = "Fearful"
}
};
}
public void HideOrShowItem(Items item)
{
item.IsVisible = true;
UpdateItems(item);
}
private void UpdateProducts(Items item)
{
var index = Items.IndexOf(item);
Items.Remove(item);
Items.Instert(index, item);
}
}
}
Your ObservableCollection Items has the type parameter string, but you are trying to create objects of type Items, which is not possible for two reasons
First of all (and this is what the compiler complains about) there is no type Items, hence you can't create instances of this type
Second, if the ObservableCollection has the type parameter string you can't add any items but strings.
You could either add strings to the collection
Items = new ObservableCollection<string>
{
"Sad",
"Angry",
"Fearful"
};
or you create the type Item (renamed it)
class Item
{
Title { get; set; }
}
and then create instances of that type:
Items = new ObservableCollection<Item>
{
new Item
{
Title = "Sad"
},
new Item
{
Title = "Angry"
},
new Item
{
Title = "Fearful"
}
};
Of course you have to change the type of Items to ObservableCollection<Item>, for the reason stated above (can't add objects of type Item to an ObservableCollection<string>).

User notification MVVM

I am trying to create a user notification. Ideally a toast-like notification that shows up in the corner for about three seconds.
I am using MVVM-light and I think the notification could be done using its messenger-service.
I have this class:
public class NotificationSync
{
public string Messages { get; set; }
}
In one viewmodel i set up the Messenger like this:
Messenger.Default.Send(new NotificationSync()
{
Messages = "message"
});
And in my MainviewModel (which is the datacontext of the view) I listen for it like this:
Messenger.Default.Register<NotificationSync>(this, (action) =>
Mess = action.Messages );
Mess is a string property on the viewmodel:
private string mess;
public string Mess
{
get { return mess; }
set
{
mess = value;
RaisePropertyChanged("Mess");
}
}
What I would like to do with mess is to bind it to my view in a toast-like manner. I.E display it for some seconds in my view. Any tips on how to do this? Thank you.
What about a Visibility property for your toast plus a timer?
Messenger.Default.Register<NotificationSync>(this, (action) =>
Mess = action.Messages
ShowToast();
);
private void ShowToast()
{
IsToastVisible = true;
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = TimeSpan.FromSeconds(3);
dispatcherTimer.Start();
}
void OnTimerTick(Object sender, EventArgs args)
{
IsToastVisible = false;
}
This assumes the textbox to which Mess is bound, is also bound to IsToastVisible and it's using a VisibilityConverter.

ViewModel trigger CollectionChanged to track ID value

I have a situation in a new WinRT app where I need to manage an ID property on a collection of objects. Essentially I'm holding the unique ID for each object which I need to increment for each new object added. This is because I'll be serializing to XML to save the data so need to manage this ID myself. If I was using SQL it would be an auto incrementing field.
The best way I could come up with was to set this using a method called from the constructor and then have a collection changed handler help me to update the value each time.
Here is the view model class:
using MM.Models;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
namespace MM.ViewModels
{
public class VehiclesViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public VehiclesViewModel()
{
Vehicles = new ObservableCollection<Vehicle>();
NewVehicle = new Vehicle();
NextVehicleID = CalculateHighestID(Vehicles.AsQueryable()) + 1;
Vehicles.CollectionChanged += new NotifyCollectionChangedEventHandler(VehicleCollectionChanged);
}
private ObservableCollection<Vehicle> _vehicles;
public ObservableCollection<Vehicle> Vehicles
{
get
{
return _vehicles;
}
set
{
if (_vehicles != value)
{
_vehicles = value;
PropertyChanged(this, new PropertyChangedEventArgs("Vehicles"));
}
}
}
void VehicleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
NextVehicleID += 1;
}
}
public Vehicle NewVehicle { get; set; }
private int _nextVehicleID;
public int NextVehicleID
{
get
{
return _nextVehicleID;
}
private set
{
_nextVehicleID = value;
PropertyChanged(this, new PropertyChangedEventArgs("NextVehicleID"));
}
}
private int CalculateHighestID(IQueryable<Vehicle> vehicles)
{
var query = vehicles.OrderByDescending(v => v.VehicleID).FirstOrDefault();
if (query != null)
{
return query.VehicleID;
}
else
{
return 1;
}
}
}
}
and here is a text button click method I added on the xaml page to add an item.
private void Button_Click_1(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
vm.Vehicles.Add(new Vehicle { VehicleID = vm.NextVehicleID });
}
However, the VehicleCollectionChanged is never called. As a test I used the same code to add a vehicle from the constructor method and that did work.
Can anyone explain why the method would not be called with adding a vehicle from the xaml button click?
Also, is there a better overall approach to keep track of an ID value for the next record?
Does your ID have to be an int (or incremental for that matter)? Could it be a GUID? At least then you could leave the creation up to the Vehicle class. As for why the event isn't being called, are you ever re-assigning the "Vehicles" property on your viewmodel? Is there a reason you have a public "set" for that property? You could potentially set a new ObservableCollection to "Vehicles" and not 1) unhook from the old event and 2) hook-up the CollectionChanged event to the new collection.
How about keeping a counter in your Vehicle class?
public class Vehicle
{
static int NextId = 1;
static object IdLock = new Object();
public int VehicleId { get; set; }
...
public Vehicle(int nextId = 0)
{
// can probably use interlocked increment instead
// of keeping a separate lock object
lock (IdLock)
{
if (nextId == 0)
{
VehicleId = NextId++;
}
else
{
NextId = nextId;
VehicleId = nextId;
}
}
}
...
}
Instead of setting NextId = 1, you may want to set it based on what is in your saved XML file. That way, it doesn't always start at 1.

Silverlight MVVM, stop SelectionChanged triggering in response to ItemsSource reset

I have two ComboBoxes, A & B, each bound to an Observable Collection. Each has a SelectionChanged trigger is attached which is intended to catch when the user changes a selection. The trigger passes the selection to a Command.
The collections implement INotifyPropertyChanged in that, in the Setter of each, an NotifyPropertyChanged event is fired. This is needed (in the MVVM approach) to notify the UI (the View) that the ComboBox's contents have changed.
The two ComboBoxes are interdependent - changing the selection in A causes B to be repopulated with new items.
Now, the problem is that B's SelectionChanged trigger fires in response to its collection being repopulated (as well as the user changing a selection). Due to the complexity of the code in the Command this is a huge waste of resources.
I could in theory stop this by not raising the NotifyPropertyChanged event when B's collection is set (because, looking at the Call Stack, this is what seems to cause the SelectionChanged trigger to fire), however the MVVM approach depends on this to keep the UI refreshed.
Any suggestions?
Why does ComboB need a SelectionChanged event? You can just bind the selected item directly into a property on the VM.
The way i have tackled this previously was to bind ComboA's selected item into the VM. In the setter for that property, i recalculate the available items for ComboB and assign them to another property on the VM, and ComboB's ItemsSource is bound to this property. Of course that property will notify (using INotifyPropertyChanged), but nothing else needed to be done, my ComboB did not have a SelectionChanged event. By using this method i didn't need a SelectionChanged on ComboA either, which keeps the view's code behind nice and sparse - everything is handled in the VM and regular databinding takes care of the rest.
Edit:
Here is an example of adjusting the required lists from within the property setters:
public class MyViewModel : INotifyPropertyChanged
{
//ItemsSource of ComboA is bound to this list
public List<SomeObject> ComboAList
{
get { return _comboAList; }
set { _comboAList = value; }
}
//ItemsSource of ComboB is bound to this list
public List<SomeObject> ComboBList
{
get { return _comboBList; }
set
{
_comboBList = value;
OnPropertyChanged("ComboBList");
}
}
//ItemsSource of the dataGrid is bound to this list
public List<SomeObject> DataGridList
{
get { return _datagridList; }
set
{
_datagridList = value;
OnPropertyChanged("DataGridList");
}
}
//SelectedItem of ComboA is bound to this property
public SomeObject FirstSelectedItem
{
get { return _firstSelectedItem; }
set
{
_firstSelectedItem = value;
RefreshListForComboB();
}
}
//SelectedItem of ComboB is bound to this property
public SomeObject SecondSelectedItem
{
get { return _secondSelectedItem; }
set
{
_secondSelectedItem = value;
RefreshListForDataGrid();
}
}
private void RefreshListForComboB()
{
//do whatever is necessary to filter or create a list for comboB
ComboBList = doSomethingThatReturnsAListForComboB();
}
private void RefreshListForDataGrid()
{
//do whatever is necessary to filter or create the list for the DataGrid
DataGridList = doSomethingThatReturnsAListForDataGrid();
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private List<SomeObject> _comboAList, _comboBList, _datagridList;
private SomeObject _firstSelectedItem, _secondSelectedItem;
}
And here is a slightly different way to do it, using a PropertyChange event handler on the VM, this simply changes where the list updating happens. This is arguably a better way of doing it than the first sample as it means the property setters don't have side effects:
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
this.PropertyChanged += new PropertyChangedEventHandler(MyViewModel_PropertyChanged);
}
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "FirstSelectedItem":
RefreshListForComboB();
break;
case "SecondSelectedItem":
RefreshListForDataGrid();
break;
}
}
//ItemsSource of ComboA is bound to this list
public List<SomeObject> ComboAList
{
get { return _comboAList; }
set { _comboAList = value; }
}
//ItemsSource of ComboB is bound to this list
public List<SomeObject> ComboBList
{
get { return _comboBList; }
set
{
_comboBList = value;
OnPropertyChanged("ComboBList");
}
}
//ItemsSource of the dataGrid is bound to this list
public List<SomeObject> DataGridList
{
get { return _datagridList; }
set
{
_datagridList = value;
OnPropertyChanged("DataGridList");
}
}
//SelectedItem of ComboA is bound to this property
public SomeObject FirstSelectedItem
{
get { return _firstSelectedItem; }
set
{
_firstSelectedItem = value;
OnPropertyChanged("FirstSelectedItem");
}
}
//SelectedItem of ComboB is bound to this property
public SomeObject SecondSelectedItem
{
get { return _secondSelectedItem; }
set
{
_secondSelectedItem = value;
OnPropertyChanged("SecondSelectedItem");
}
}
private void RefreshListForComboB()
{
//do whatever is necessary to filter or create a list for comboB
ComboBList = doSomethingThatReturnsAListForComboB();
}
private void RefreshListForDataGrid()
{
//do whatever is necessary to filter or create the list for the DataGrid
DataGridList = doSomethingThatReturnsAListForDataGrid();
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private List<SomeObject> _comboAList, _comboBList, _datagridList;
private SomeObject _firstSelectedItem, _secondSelectedItem;
}