EDIT #2:
public void ReverseOrderRegBtns()
{
ICollectionView view = CollectionViewSource.GetDefaultView(GlobalObservableCol.regBtns);
view.SortDescriptions.Add(new SortDescription("ReverseOrder", ListSortDirection.Ascending));
view.Refresh();
}
Not really sure how to fit the ICollectionView in, it also interrupts timers that are doing some logic. For some reason it interupts my timing that starts counting when a button gets created.
My application has several timers, so the buttons that get created get updates with possible new content every x amount of seconds.
EDIT:
The databinding looks as following:
<ListBox x:Name="lbRegistration" ItemsSource="{Binding regBtnsReverse, ElementName=Window}" Background="{x:Null}"
Old:
public ObservableCollection RegBtns
{
get
{
if (GlobalObservableCol.regBtns == null)
{
return new ObservableCollection();
}
return GlobalObservableCol.regBtns;
}
}
New (no longer works), the reverse is in the ObserableCol class
public IEnumerable<RegistrationButton> RegBtns
{
get
{
return GlobalObservableCol.regBtnsReverse;
}
}
I have created the IEnumerable in my a static class where the ObservableCollection list is available.
I cannot seem to bind when my databinding is a IENumerable. How to bind the NumbersReverse
method that is located in a other class to my MainWindow.xls?
I have a ObservableCollection that holds custom styled WPF buttons.
Those items get dynamically created and added on a custom slider control (listbox).
For example Button 1, Button 2, Button 3, ...
What i want is exactly the opposite.
For example Button 3, Button 2, Button 1.
This sounds like a linked list but my entire application is already using this Observable Collection throughout the entire logic.
When the buttons get displayed in the UI the last added button should be placed as the first item on my slider (that is a listbox control).
How exactly can i pull this off?
Thanks,
PeterP
<ListBox ItemsSource="{Binding Foo}"/>
private ObservableCollection<RegButtton> RegBtns = new...;
public ICollectionView Foo { get; set; }
Foo = CollectionViewSource.GetDefaultView(RegBtns);
Foo.SortDescriptions.Add(new SortDescription("SomePropertyName", ListSortDirection.Ascending));
Foo.Refresh();
Somewhere in your code there should be myCollection.Add( someButton ).
Replace it with myCollection.Insert( 0, myButton ) and you're finished.
I do not know what framework you use. I would do something like in my view model:
public ObservableCollection<int> Numbers { get; private set; }
public IEnumerable<int> NumbersReverse
{
get
{
return Numbers.Reverse();
}
}
and I would use NumbersReverse as a datasource:
<ListBox Name="listBox1" ItemsSource="{Binding NumbersReverse}"/>
Of course there are many other ways of doing that. You have to figure out what's best in your case...
Edit:
e.g. if you do not care about the order of the items for any other purpose, then the solution provided by stijn is much better.
Related
I want to display all my CONTACTS in a ItemsControl dynamically.
I have a List Contacts in my Logic (this one gets updated if someone removed me or if someone accepted my request) and I've added this List to a ObservableCollection<> which is bound to the ListBox.
C#
Contacts = new ObservableCollection<Contact>(MyLogic.Current.Contacts);
XAML
<ItemsControl ItemsSource="{Binding Contacts}" x:Name="MainPanel">
And here's the problem:
When I want to add a contact to my Contacts LIST, the ObservableCollection doesn't get updated
MyLogic.Current.Contacts.Add(new Contact("Fred", true));
This is not the best solution but if you want to see where the problem is, the following code updates your UI:
var newContact = new Contact("Fred", true));
MyLogic.Current.Contacts.Add(newContact);
Contacts.Add(newContact);
A better solution is when MyLogic.Current.Contacts changes notify your UI via events.
Edit:
The problem is that I can only update the LIST and not the
ObservableCollection(the list itself is in a different project)... so
I need a way to update the GUI when that LIST is updated
To notify UI when ever your data changes you can use events as follow:
First define an EventArgs which shows newly added items like this:
public class ModelAddedEventArgs<TModel> : EventArgs
{
public ModelAddedEventArgs(TModel newModel)
{
NewModel = newModel;
}
public TModel NewModel { get; set; }
}
Then define an EventHandler in your MyLogic calss as follow:
public event EventHandler<ModelAddedEventArgs<Contact>> ContactAdded;
public void AddModel(Contact model)
{
// first add your contact then:
if (ActivityGroupAdded != null)
ActivityGroupAdded(this, new ModelAddedEventArgs<Contact>(model));
}
And finally use your EventHandler to notify UI:
private void YourUIConstructor()
{
MyLogic += OnContactAdded;
}
private void OnContactAdded(object sender, ModelAddedEventArgs<Contact> e)
{
Contacts.Add(e.NewModel);
}
ObservableCollection is not work with source List. When you create it from existing List it only copies elements from it to inner storage.
So, you should add elements to ObservableCollection itself to get what you want etc.
The problem you are having is that you are expecting the ObservableCollection to be automatically updated when you add to your List. This is not true.
When you instantiate your ObservableCollection, you are taking a copy of your List, not a reference to the list itself.
Therefore, when you add a new item, you should add to your ObservableCollection, not your List. Or both. But I can't see why you would need both, I would recommend ditching your List altogether.
First of all, what I'm trying to do is a "simple" binding of a ComboBox to my source.
The structure is something like:
public class Data
{
public ObservableList<string> List {get;set;}
public string Selected {get;set;}
}
Also, it implements INotifyPropertyChanged interface.
My problem is, i found several solutions to do this via XAML, unfortunately i can't do it with XAML since my ComboBoxes have to be generated during runtime.
So my question is, how i can bind my ComboBox to Data.List, and also the selected item (value?) to Data.Selected, and this one should be TwoWay so my Data class knows that something was selected. Keep in mind this has to be through c# code (XAML is no option unfortunately).
Thanks in advance. :)
It's pretty easy. Assuming, that Data has properties instead of fields:
public class Data
{
public Data()
{
List = new ObservableCollection<string>
{
"Apple", "Orange", "Lime"
};
}
public ObservableCollection<string> List { get; private set; }
public string Selected { get; set; }
}
you can write this:
var comboBox = new ComboBox
{
DataContext = new Data()
};
comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding("List"));
comboBox.SetBinding(ComboBox.SelectedItemProperty, new Binding("Selected")
{
Mode = BindingMode.TwoWay
});
To add ComboBox into visual tree, just call proper method for the container. E.g., this will work with any ContentControl (like Window):
AddChild(comboBox);
how i can bind my combobox to Data.List, and also the selected item (value?)
Create a custom composite user control which contains the combobox. Map the combobox's properties to two dependencies properties created on the custom control, one to load the data and the other to provide an on demand selected item's data. Any plumbing needs are done inside the codebehind which ultimately provides all the magic.
Then you can create/bind this control dynamically in codebehind as needed in the other page you are working on.
Sounds like a sort of "recursive binding". If your combos are in a container control, what you need is bound the container to a collection of your single combo model, so each view in the ItemsControl will be bound to a single combo model.
I'm trying to use WPF binding to an object that is contained by the DataContext object and it is not working.
If I place all binding elements in the DataContext ViewModel object, everything works, but if I separate out the data list and data elements into a separate class that is contained by the ViewModel class, the ListBox data will work, but the binding to the individual data elements is not working.
With my experimentation, I assume that the bound object needs to be directly bound to the DataContext ViewModel class. I can do that and it works, but it's not as object oriented or reusable as I would like it to be. I've separated out the data list for the ListBox into it's own class and I'm assuming that since it is an ObservableCollection it works regardless of it being attached to the contained object. Since the individual data elements of the objects are only notified through OnPropertyChanged, no matter what I've tried, I can't get the WPF form to recognize the data, even though the DataContext.CurrentRecord shows the correct data. I can only assume that the OnPropertyChanged notification are not going where I need them to go.
Partial Code for DataContext ViewModel is as follows:
public ObservableCollection<ItemModel> Items
{
get { return ItemListMgr.Items; }
set { ItemListMgr.Items = value; }
}
public ItemModel CurrentItem
{
get { return ItemListMgr.CurrentItem; }
set { ItemListMgr.CurrentItem = value; }
}
Corresponding code in Contained ItemListMgr object is as follows:
public readonly ItemListModel _ItemList;
public ObservableCollection<ItemModel> Items
{
get { return _ItemList.Items; }
set
{
_ItemList.Items = value;
OnPropertyChanged("Items");
}
}
private ItemModel _currentItem;
public ItemModel CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
OnPropertyChanged("CurrentItem");
}
}
The "Items" object is the list that gets displayed and even using the contained object "ItemListMgr", this part still works great. As the user scrolls through the list CurrentItem is set to the active item in "Items", but even though the WPF data entry elements are bound to the CurrentItem elements, they will not change when I scroll through the list.
The Xaml code to bind a text box is as follows:
Text="{Binding CurrentItem.ItemName, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}"
The Xaml code binds to the DataContext object, which is using a simple pass through to the contained ItemListMgr class. I've tried to add the OnPropertyChange to the pass through elements, but that didn't help and I would guess only added to the confusion.
As this is a large amount of code and fairly complex, I hope that I've given enough key elements for you to understand what I'm doing. Hopefully there is a way to allow this type of binding as it will greatly enhance what I'm doing.
Whatever help you can give will be greatly appreciated,
Paul
How is ICollectionViewLiveShaping implemented for the purpose of filtering? Is it something like:
public ICollectionView WorkersEmployed { get; set; }
WorkersEmployed = new CollectionViewSource { Source = GameContainer.Game.Workers }.View;
I'm not using GetDefaultView because I need multiple instances of filters on this collection. If it matters, GameContainer.Game.Workers is an ObservableCollection.
ApplyFilter(WorkersEmployed);
private void ApplyFilter(ICollectionView collectionView)
{
collectionView.Filter = IsWorkerEmployed;
}
public bool IsWorkerEmployed(object item)
{
Worker w = item as Worker;
return w.EmployerID == this.ID;
}
This all works, but of course it must be manually refreshed, which is why I'm trying to use ICollectionViewLiveShaping. How does live filtering working?
Update: It appears that the only way to add a property to ICollectionViewLiveShaping's LiveFilteringProperties collection is via a string. Given that limitation, is it even possible to filter by properties in another class (Workers' EmployerID in this case)?
Is what I'm trying to do in this situation is even a viable option?
All you need to do is add a property in LiveFilteringProperties for which you want the filter to call on property change and set IsLiveFiltering to true for your collection to enable live filtering.
Make sure PropertyChanged event gets raised whenever EmployerID property changes i.e. your Worker class should implement INotifyPropertyChangedEvent.
This will work then -
public ICollectionViewLiveShaping WorkersEmployed { get; set; }
ICollectionView workersCV = new CollectionViewSource
{ Source = GameContainer.Game.Workers }.View;
ApplyFilter(workersCV);
WorkersEmployed = workersCV as ICollectionViewLiveShaping;
if (WorkersEmployed.CanChangeLiveFiltering)
{
WorkersEmployed.LiveFilteringProperties.Add("EmployerID");
WorkersEmployed.IsLiveFiltering = true;
}
We are using WPF + MVVM + Visual Studio 2017.
We want to convert this to add live filtering:
public ObservableCollection<RowViewModel> Rows { get; set; }
The method below has two key advantages:
It's designed to work efficiently with the WPF runtime to minimise on-screen rendering using bulk updates.
So it's fast.
And because the boilerplate code is listed below, it's easier to follow compared to any other docs you will find on the web.
Please let me know if this worked for you, any issues and I'll update the instructions to make easier.
And the steps:
Step 1: Non-notifying Collection Wrapper
Create a special ObservableCollection that does not fire update events. This is a one-off. We want to fire the update bulk update event ourselves, which is faster.
public class NonNotifyingObservableCollection<T> : ObservableCollection<T>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { /* Do nothing */ }
}
Step 2: Convert to NonNotifyingObservableCollection
Convert to a private variable which uses this new collection.
private NonNotifyingObservableCollection<RowViewModel> rows;
// ... and in constructor
rows = new NonNotifyingObservableCollection<RowViewModel>();
Step 3: Add Wrapper
Add these variables:
private ICollectionView rowsView;
public ICollectionViewLiveShaping RowsLiveView { get; set; }
And in the Initialise() call after the ViewModel is constructed (or perhaps in the constructor):
// Call on the dispatcher.
dispatcher.InvokeAsync(() =>
{
this.rowsView = CollectionViewSource.GetDefaultView(this.rows);
this.rowsView.Filter = o =>
{
// This condition must be true for the row to be visible on the grid.
return ((RowViewModel)o).IsVisible == true;
};
this.RowsLiveView = (ICollectionViewLiveShaping)this.rowsView;
this.RowsLiveView.IsLiveFiltering = true;
// For completeness. Changing these properties fires a change notification (although
// we bypass this and manually call a bulk update using Refresh() for speed).
this.RowsLiveView.LiveFilteringProperties.Add("IsVisible");
});
Step 4: Add items
Now we add items to the backing collection, then call .Refresh() to refresh the view:
this.rowsView.Add(new RowViewModel( /* Set properties here. */ ));
We then bind the grid to RowsLiveView, (instead of binding to Rows in the original code).
Step 5: Update live filtering
Now we can update the IsVisible property, then call .Refresh() to redraw the grid.
rows[0].IsVisible=false;
this.rowsView.Refresh(); // Hides the first row.
Update
Update: This answer could be simplified. The whole point of ICollectionViewLiveShaping is to autorefresh without the need to call .Refresh(). Given that we have a NonNotifyingObservableCollection and we are manually controlling everything with a .Refresh(), could remove public ICollectionViewLiveShaping RowsLiveView { get; set; } and, directly to RowsView (make it a property with { get; set; }, and use normal ObservableCollection<>. In other words - ICollectionViewLiveShaping is great for a small amount of rows (e.g. <100), but for anything more, ICollectionView in combination with a bulk update and a manual Refresh() is better from a speed point of view.
I experimented with this and it looks like it is not designed for what you (and me) want: Automatic filtering when you change filtering conditions. It filters automatically when some properties of DataGrid's item source changes, but not when filter conditions change - you must call ICollectionViewSource.Refresh manually.
Ive got two DataGrids. EmployeeGrid and WorkSessionsGrid. Each Employee has a list of WorkSessions that I want the user to access by selecting an Item in the EmployeeGrid which should make the WorkSessionsGrid generate the WorkSessions for the selected Employee. Why is the following not correct?
<DataGrid Name="dg_2" ItemsSource="{Binding ElementName=dg_1, Path=SelectedItem.WorkSessions}"/>
Update
I've come to the conclusion that the problem has to be in one of the other layers.
Here's the remainder of my code, hopefully someone is capable of helping me out.
Is there something fundamentally that I am missing?
Code-Behind xaml
public partial class MainWindow : Window
{
public EmployeeViewModel EmployeeViewModel = new EmployeeViewModel();
public MainWindow()
{
InitializeComponent();
menu_employee.DataContext = EmployeeViewModel;
sp_employee.DataContext = EmployeeViewModel;
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
sp_worksessions.DataContext = EmployeeViewModel.SelectedEmployee.WorkSessions;
menu_worksession.DataContext = EmployeeViewModel.SelectedEmployee.WorkSessions;
datagrid_worksessions.ItemsSource = EmployeeViewModel.SelectedEmployee.WorkSessions;
}
}
WorkSessionViewModel
class WorkSessionViewModel : ViewModelBase
{
private WorkSessions _workSessionsModel = new WorkSessions();
public WorkSessions WorkSessionsView = new WorkSessions();
private WorkSessionModel _selectedWorkSession = new WorkSessionModel();
public WorkSessionModel SelectedWorkSession
...
WorkSessionModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
Try Binding to the Element in stead.
<DataGrid Content="{Binding ElementName=ListOfEmp, Path=SelectedItem.Name}" DataContext="{Binding}" />
This bit of XAML looks quite correct, try to debug the binding, there might be some other problems like visual tree breaks or the WorkSessions collection perchance is a field and not a property etc.
If there are binding errors please share them.
As #H.B. has pointed out correctly, please use your Visual Studio's Output Window to see any binding errors. They will tell you if the bindings are failing. If you find binding erros then yours binding should be solved for two possible issues...
Source of data is incorrect. Is the data context and items source correctly set for that UI element such as DataGrid?
Path of the property in the binding could be incorrect. Is your SelectedItem having an object has any property named WorkSessions in it? etc.
Apart from this we still dont know what dg_1 and dg_2 from your XAML is. Your code behind shows different names datagrid_employees and datagrid_worksessions I guess.
You should add one more item for the EmployeeViewModel, called: SelectedEmployee and bind that with the employee grid selected item, mode=TwoWay.
Then you databind for the second grid should be:
<DataGrid Name="dg_2" ItemsSource="{Binding Path=SelectedEmployee.WorkSessions}"/>
Since both grids are in the same window, thus, you should only set datacontext for the windows only. In side the viewmodel, you have 2 dependency properties: EmployeeList, SelectedEmployee. Whereas, EmployeeList is binded to ItemsSource of the employee grid. SelectedEmployee is binded to SelectedItem on the employee grid.