I need to modify a badly written program in order to add a new functionality while changing as least as possible of the original code.
The task is the following: I have 4 listbox on which items are programmatically added using listbox.items.add(item) at random intervals. They don't use datasource. I need to detect when some of the listbox stops adding new items and send an email alerting the situation.
I created a new class and tried to bind the items to a BindingList or ObservableCollection without success. This is what I got by now:
On main form:
listBoxTime1.DataBindings.Add(new Binding("Items", _inactividadController, "Items1")
{
DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged,
ControlUpdateMode = ControlUpdateMode.Never //because listbox.items is read only
});
On my _inactividadController:
public ListBox.ObjectCollection Items1 { get; set; }
The binding works, I can access items as an array, but I'd love to convert Items1 to ObservableCollection so I can benefit from INotifyCollectionChanged.
Any other approach is welcome.
Thanks
Related
I have this code:
public ObservableCollection<CVM> SObservable{ get; set; }
this.scores = App.DB.GetCardFace(0);
this.SObservable = new ObservableCollection<CVM>(this.scores);
I then assign this as a data source:
this.columnSeries = new ColumnSeries() {
ItemsSource = SObservable
};
chart.Series.Add(this.columnSeries);
My chart displays correctly
Now I change the data:
this.scores = App.DB.GetCardFace(1);
but nothing happens to the chart. I suspect the SObservable contents are not changing.
Can someone give me advice on how I should go about changing the contents of the SObservable so it triggers a change that can be picked up by the chart code.
an ObservableCollection does not contain any references to the data that was used to populate it in it's constructor, so a change to that original data will not be reflected in the ObservableCollection
SObservable.Clear();
foreach(var s in scores)
{
SObservable.Add(s);
}
I have an ItemsControl displaying a collection of files. Those files are sorted by most recent modification, and there's a lot of them.
So, I want to initially only show a small part (say, only 20 or so) of them, and display a button labelled "Show More" that would reveal everything when clicked.
I already have a solution, but it involves using a good old LINQ Take on my view model's source property. I was wondering if there was a cleaner way.
Thanks.
Why not have the object that you assign to the ItemsSource handle this logic - on first assignment, it would report a limited subset of the items. When Show More is clicked, the object is updated to show more (or all entries) and then notifies the framework that the property has changed (e.g. using the IPropertyNotifyChanged).
public class MyItemSource
{
private List<string> source = { ... };
public MyItemSource()
{
this.ShowThisMany = 20;
}
public int ShowThisMany
{
get;
set; // this should call\use the INotifyPropertyChanged interface
}
public IEnumerable<string> this[]
{
return this.source.Take(this.ShowThisMany);
}
}
...
MyItemsSource myItemsSource = new MyItemsSource();
ItemsControl.Source = myItemsSource;
...
void OnShowMoreClicked(...)
{
myItemsSource.ShowThisMany = 50;
}
In order to do this, you need to create some sort of 'view' on your data. There is nothing within the WPF framwork that will give you this functionality for free. In my opinion, a simple bit of Linq, Take(), is a clean and simple solution.
I have a listbox on my WinForms where users can move the items up and down and that listbox is as well the same as a list I have and I was wondering what would be the most efficient way to maintain both synchronized.
for example to move an item down I have:
int i = this.recoveryList.SelectedIndex;
object o = this.recoveryList.SelectedItem;
if (i < recoveryList.Items.Count - 1)
{
this.recoveryList.Items.RemoveAt(i);
this.recoveryList.Items.Insert(i + 1, o);
this.recoveryList.SelectedIndex = i + 1;
}
And I have:
public List<RouteList> Recovery = new List<RouteList>();
Which I would like to maintain updated against the listbox.
Should I simple clear Recovery and update with the current listbox data or is there a better way to update both when move up and down ?
I am mainly asking because the types from the listbox to the list are different.
.Net provides built-in support for this type of behavior. In order to use it, you need to change the type of your Recovery list to:
public BindingList<RouteList> Recovery = new BindingList<RouteList>();
And then you use that BindingList as the DataSource in your controls:
listBox1.DataSource = Recovery;
Here's a simple example using a BindingList of String. I have two listBox's on the form, and they both stay in sync as the selected element gets swapped with the first element in the list:
public partial class Form1 : Form
{
private readonly BindingList<string> list = new BindingList<string> { "apple", "pear", "grape", "taco", "screwdriver" };
public Form1()
{
InitializeComponent();
listBox1.DataSource = list;
listBox2.DataSource = list;
}
private void listBox1_KeyUp(object sender, KeyEventArgs e)
{
var tmp = list[0];
list[0] = list[listBox1.SelectedIndex];
list[listBox1.SelectedIndex] = tmp;
}
}
The proper way is to change the underlying object and then have the UI Control react to that change.
For the ListBox to react to changes in your object collection (your List) you'd need to use an ObservableCollection instead. It's like the INotifyPropertyChanged for collections.
Then you make your up/down actions change the collection, NOT the UI.
EDIT
I am not saying to add an observer on TOP of the collection. I'm saying to change the type of your collection. Don't use List, use ObservableCollection. It works (largely) the same way but notifies the bound UI Controls of changes to it's items.
As for an example, please Google for it. That's what i'd have to do to provide one anyway..
I'm trying to bind a collection to a DataGridView. As it turns out it's impossible for the user to edit anything in this DataGridView although EditMode is set to EditOnKeystrokeOrF2.
Here is the simplified code:
public Supplies()
{
InitializeComponent();
List<string> l = new <string>();
l.Add("hello");
this.SuppliesDataGridView.DataSource = l;
}
It also doesn't work when I change the collection type to SortableBindingList, Dictionary or even use a BindingSource.
What can be wrong here?
For me the following method works as expected:
Open your form (usercontrol, etc.) with the designer
Add a BindingSource to your form
Select the BindingSource in your form and open the properties page
Select the DataSource property and click on the down arrow
Click on Add project data source
Select Object
Select the object type you wish to handle
This should be the type that will be handled by your collection, not the CustomCollection itself!
Show the available data sources by selecting from the MenuBar Data - Show Data Sources
Drag and Drop your ItemType from the DatasSources on your form
Go into the code of your form and bind your CustomCollection to the BindingSource
var cc = new CustomCollection();
bindingSource1.DataSource = cc;
Remarks:
The DataGridView is just the last part in your chain to (dis)allow changing, adding and removing objects from your list (or CustomCollection). There is also a property AllowNew within the BindingSource and the ICollection interface has a property IsReadOnly which must be set to false to allow editing. Last but not least, the properties of your class within the collection must have a public setter method to allow changing of a value.
Try this:
public class CustomCollection { public string Value { get; set; } }
public Supplies()
{
InitializeComponent();
List<CustomCollection> l = new List<CustomCollection> { new CustomCollection { Value = "hello" } };
this.SuppliesDataGridView.DataSource = l;
}
Once you've set the DataSource property you'll then want to fire off the DataBind() method.
this.SuppliesDataGridView.DataSource = l;
this.SuppliesDataGridView.DataBind();
UPDATE:
As you rightly pointed out in the comments, the DataBind() method doesn't exist for this control.
This link might provide some helpful information: http://msdn.microsoft.com/en-us/library/fbk67b6z%28v=VS.90%29.aspx
This little bit of code will help me describe my problem:
public class Car
{
...
}
public class CarQueue : ObservableCollection<Car>
{
public IEnumerable Brands
{
get { return (from Car c in this.Items select c.Brand).Distinct(); }
}
}
Ok now I have an instance of CarQueue class bound to a DataGrid. When I add a Car object to the queue the datagrid updates fine by itself, but I also have a listbox bound to the 'Brands' property which doesn't update. Here is a simple sequence of code to explain:
CarQueue cq = new CarQueue();
DataGrid1.ItemsSource = cq;
ListBox1.ItemsSource = cq.Brands; // all above done during window load
...
Car c;
cq.Add(c); // datagrid updates, but not listbox
Does the listbox not update because it is bound to a property with dynamic LINQ query?
One other thing I tried was inheriting INotifyPropertyChanged and adding a new event handler to the CollectionChanged event (in my CarQueue constructor):
this.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CarQueue_CollectionChanged);
Then in the event handler:
void CarQueue_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("Brands"));
}
This didn't work either. So does anyone know what the problem is? Thanks
There are a couple of problems here.
The Brands property is a sequence built on the fly by LINQ when it is asked for it. WPF only asks for it during the initial binding: it has no way of knowing that if it were to ask again it would get a different answer, so it doesn't. To get WPF to track changes to the Brands collection, you would need to expose Brands as a collection, and have INotifyCollectionChanged implemented on that collection -- for example by making Brands an ObservableCollection. One way to do this is using Bindable LINQ.
As an alternative, your second approach, of raising a PropertyChanged event for Brands, can be made to work. However, in order for this to work, you have to bind ItemsSource to Brands. (At the moment, you are assigning it, which means WPF forgets where the collection came from and just keeps its private copy of the values.) To do this, either use the {Binding} markup extension in XAML:
<ListBox ItemsSource="{Binding Brands}" /> <!-- assumes DataContext is cq -->
or use BindingOperations.SetBinding:
BindingOperations.SetBinding(ListBox1, ListBox.ItemsSourceProperty,
new Binding("Brands") { Source = cq });