When collection changed in ObservableCollection add new entries to ReactiveList - c#

I have an ObservableCollection which getting filled from a TcpClient. When new data arrives (new Items are added), I want to create new Buttons inside an ItemsControl. It works the old way (with CollectionChanged) but I don't get it work with ReactiveUI.
I'm very new to ReactiveUI, and its quite hard for me to getting started. Could you may help me by putting me on the right path or maybe by providing some sample code?
The Idea:
public class ChooseMachineViewModel : ReactiveObject
{
public ReactiveList<Button> ButtonList { get; set; }
private Dictionary<ushort, Button> addressToButton;
//This one is normaly in another class and will be filled by a TcpClient
public readonly ObservableCollection<WWSS.Message.CUStatus> ControlUnitsStatus;
public ChooseMachineViewModel()
{
//TODO: Make this Reactive!
//The ButtonList for an ItemsControl
ButtonList = new ReactiveList<Button>();
//The Dictonary for addresses -> Button
addressToButton = new Dictionary<ushort, Button>();
//The ObservableCollection filled by a TCP Server
ControlUnitsStatus.CollectionChanged += ControlUnitsStatus_CollectionChanged;
}
private void ControlUnitsStatus_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (WWSS.Message.CUStatus stat in e.NewItems)
{
TryAddButton(stat);//When new Status arrive, try to create new button
}
}
if (e.OldItems != null)
{
foreach (WWSS.Message.CUStatus stat in e.OldItems)
{
TryRemoveButton(stat);//When Status removed, try to remove the button
}
}
}
private bool TryAddButton(WWSS.Message.CUStatus status)
{
if (!addressToButton.ContainsKey(status.Address))//if the Address is already in the dictonary don't create a button
{
var but = new Button { Content = status.Address.ToString() };
addressToButton.Add(status.Address, but);
ButtonList.Add(but);
return true;
}
return false;
}
private void TryRemoveButton(WWSS.Message.CUStatus status)
{
if (addressToButton.ContainsKey(status.Address))
{
ButtonList.Remove(addressToButton[status.Address]);
addressToButton.Remove(status.Address);
}
}
}

The trick was to use CreateDerivedCollection:
public class ChooseMachineViewModel : ReactiveObject
{
public IReactiveDerivedList<Button> ButtonList { get; set; }
public ChooseMachineViewModel(ObservableCollection<CUStatus> source)
{
addressToButton = new Dictionary<ushort, Button>();
ButtonList = ControlUnitsStatus.CreateDerivedCollection(status => new Button { Content = status.Address.ToString() },
status => !ButtonList.Any(button => button.Content.ToString().Equals(status.Address.ToString())));
}
}

Related

Removing item from ListBox - unhandled exception in WPF?

I have this code in my MainWindow.
The user enters Name, Phone and Email in the fields provided, selects Location and then the Name appears in the listbox, lstClients.
I'm trying to write a method to remove a selected name from the listbox.
namespace WpfApp_Employment_Help
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// client list
List<Client> ClientList = new List<Client>();
public MainWindow()
{
InitializeComponent();
}
// method to select location via radio button
public string GetSelectedLocation()
{
string selected = string.Empty;
if (RBLocE.IsChecked == true) { selected = "Edinburgh"; }
else if (RBLocG.IsChecked == true) { selected = "Glasgow"; }
else if (RBLocO.IsChecked == true) { selected = "Other"; }
return selected;
}
// method to create a new client on click
private void newClient(object sender, RoutedEventArgs e)
{
Client c = new Client(boxClientName.Text, boxClientPhone.Text, boxClientEmail.Text, GetSelectedLocation());
boxClientName.Clear();
boxClientPhone.Clear();
boxClientEmail.Clear();
ClientList.Add(c);
lstClients.ItemsSource = null;
lstClients.ItemsSource = ClientList;
}
// method to id selected client
private void AssignID(object sender, RoutedEventArgs e)
{
Client c = lstClients.SelectedItem as Client;
if (c != null)
{
c.AssignID();
}
lstClients.ItemsSource = null;
lstClients.ItemsSource = ClientList;
}
// method to remove selected client
private void RemoveClient(object sender, RoutedEventArgs e)
{
lstClients.Items.Remove(lstClients.SelectedItem);
}
}
}
When I run this code, I get Unhandled Exception:
System.InvalidOperationException: 'Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.'
how can I rewrite my RemoveClient method?
my code for the Client class is this:
public partial class Client
{
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Location { get; }
public bool IDed { get; private set; }
public Client(string n, string p, string e, string l)
{
Name = n;
Phone = p;
Email = e;
Location = l;
}
}
I have Visual Studio 2022 which has been recently updated.
I have also tried the following solution but it gives me another unhandled error?
It looks like I need to change List </string/> and string to something else. but what?
private void RemoveClient(object sender, EventArgs e)
{
if (lstClients.Items.Count >= 1)
{
if (lstClients.SelectedValue != null)
{
var items = (List<string>)lstClients.ItemsSource;
var item = (string)lstClients.SelectedValue;
lstClients.ItemsSource = null;
lstClients.Items.Clear();
items.Remove(item);
lstClients.ItemsSource = items;
}
}
else
{
System.Windows.Forms.MessageBox.Show("No ITEMS Found");
}
}
System.InvalidCastException: 'Unable to cast object of type 'System.Collections.Generic.List`1[WpfApp_Employment_Help.Client]' to type 'System.Collections.Generic.List`1[System.String]'.'
As the error message suggests, you can't modify the ItemsControl via the collection view returned from the ItemsControl.Items property.
WPF is generally designed to work on the data sources instead of handling the data related controls (data presentation). This way data and data presentation (GUI) are cleanly separated and code will become a lot simpler to write.
In case of the ListView (or ItemsControl in general), simply modify the source collection.
To improve performance, the source collection should be a INotifyCollectionChanged implementation, for example ObservableCollection<T>, especially when you expect to modify the source collection.
This makes invalidating the ItemsSource e.g. by assigning null, just to set it again redundant and significantly improves the performance.
public partial class MainWindow : Window
{
// client list
public ObservableCollection<Client> ClientList { get; } = new ObservableCollection<Client>();
// method to create a new client on click
private void newClient(object sender, RoutedEventArgs e)
{
Client c = new Client(boxClientName.Text, boxClientPhone.Text, boxClientEmail.Text, GetSelectedLocation());
boxClientName.Clear();
boxClientPhone.Clear();
boxClientEmail.Clear();
ClientList.Add(c);
// The following lines are no longer needed
// as the GUI is now notified about the collection changes (by the INotifyCollectionChanged collection)
//lstClients.ItemsSource = null;
//lstClients.ItemsSource = ClientList;
}
// method to id selected client
private void AssignID(object sender, RoutedEventArgs e)
{
Client c = lstClients.SelectedItem as Client;
// Same as the if-statement below
c?.AssignID();
//if (c != null)
//{
// c.AssignID();
//}
// The following lines are no longer needed
// as the GUI is now notified about the collection changes (by the INotifyCollectionChanged collection)
//lstClients.ItemsSource = null;
//lstClients.ItemsSource = ClientList;
}
// method to remove selected client
private void RemoveClient(object sender, RoutedEventArgs e)
{
var clientToRemove = lstClients.SelectedItem as Client;
this.ClientList.Remove(clientToRemove);
}
}
If you change the type of ClientList from List<Client> to ObservableCollection<Client>, you could simply remove the item directly from the source collection:
public partial class MainWindow : Window
{
ObservableCollection<Client> ClientList = new ObservableCollection<Client>();
public MainWindow()
{
InitializeComponent();
}
...
private void RemoveClient(object sender, RoutedEventArgs e)
{
ClientList.Remove(lstClients.SelectedItem as Client);
}
}

Xamarin.Forms ListView Load More

The Problem
What I want to achieve can you basically see here
so when the user scrolls to the end I want to load more, because my List is very large and I want to Maximize Performance.
I'm trying to achieve this as follows, in splitting the Main Collection with the Data so that i can set the ItemSource new when the User reaches the end.
What ive Implemented so far
public class ViewModel : BaseViewModel {
public ViewModel() {
Initialize();
}
public List<List<Usermodel>> SplitedUserLists { get; set; }
//Main List that im Binding to
public List<Usermodel> ItemSourceCollection { get; set; }
public int ChunkSize { get; set; }
#endregion
private async void Initialize() {
ItemSourceCollection = await LoadList();
// Splites the list (in this case the chunk Size is 5)
SplitedScoreLists = ItemSourceCollection.Split(GetChunkSize()));
ItemSourceCollection = SplitedScoreLists[0];
}
//Gets called from CodeBehind
public void ListViewItemAppearing(ItemVisibilityEventArgs e) {
//Bottom Hit!
if (e.Item == ItemSourceCollection[ItemSourceCollection.Count - 1]) {
if (ChunkSize >= SplitedScoreLists.Count) {
return;
}
foreach (var usermodel in SplitedScoreLists[ChunkSize].ToList()) {
ItemSourceCollection.Add(usermodel);
}
if (ChunkSize < SplitedScoreLists.Count) {
ChunkSize++;
}
}
}
}
Questions
The problem with my Implementation is that the List is actually longer than the Count of the original List due to duplicates.
Is this the right way to achieve something like this?
Am I actually increasing Performance with this? I need to cause the List is 1000+ entries.
There are nice tutorials on how to achieve this:
http://motzcod.es/post/107620279512/load-more-items-at-end-of-listview-in
https://github.com/jguibault/Xamarin-Forms-Infinite-Scroll
http://www.codenutz.com/lac09-xamarin-forms-infinite-scrolling-listview/
The key point is when to raise the "load more" command:
public class InfiniteListView : ListView
{
public static readonly BindableProperty LoadMoreCommandProperty = BindableProperty.Create<InfiniteListView, ICommand>(bp => bp.LoadMoreCommand, default(ICommand));
public ICommand LoadMoreCommand
{
get { return (ICommand) GetValue(LoadMoreCommandProperty); }
set { SetValue(LoadMoreCommandProperty, value); }
}
public InfiniteListView()
{
ItemAppearing += InfiniteListView_ItemAppearing;
}
void InfiniteListView_ItemAppearing(object sender, ItemVisibilityEventArgs e)
{
var items = ItemsSource as IList;
if (items != null && e.Item == items[items.Count - 1])
{
if(LoadMoreCommand != null && LoadMoreCommand.CanExecute(null))
LoadMoreCommand.Execute(null);
}
}
}

How can I pair objects to radio buttons?

I'm working on a small form app, and I have "paired" my radio buttons with lists in a common class. The purpose of this was to turn on/off the corresponding list
public class myType
{
public RadioButton button { get; set; }
public ListBox list { get; set; }
}
I proceed to create these pairs through a for loop inside an array
for (int i = 0; i < broj_botuna; i++)
{
theArray[i] = new myType();
}
I use a common event handler for all the radio buttons:
private void test_CheckedChanged(object sender, EventArgs e)
{
var xx = sender as RadioButton;
//do stuff
positionInArray = Array.IndexOf(theArray, xx);
}
except that the last line of code "xx" should be of type "myType" and not "radioButton" that I managed to retrieve.
So could anyone tell me how do I get the reference from "radioButton" to "myType"? Or is there a better alternative?
You can use Array.FindIndex like:
var positionInArray = Array.FindIndex(theArray, b => b.button == xx);
You could create some constructs that allow you to easily associate your properties to the parent object if you wanted to.
This approach would allow you to always reference your parent type provided that you added a bit more code in your get's and set's.
static void Main()
{
myType item = new myType();
var button = new Button();
myType.button = button;
var list = new ListBox();
myType.list = list;
item = list.GetParent();
bool isSameButton = button == item.button;
bool isSameList = list == item.list;
Assert.IsTrue(isSameButton);
Assert.IsTrue(isSameList);
}
public class myType
{
private RadioButton _button;
public RadioButton button
{
get { return _button; }
set {
value.AssociateParent(this);
_button = value;
}
}
private ListBox _list;
public ListBox list
{
get { return _list; }
set {
value.AssociateParent(this);
_list= value;
}
}
}
public static class Extensions
{
private static Dictionary<object, object> Items { get; set; }
static Extensions()
{
Items = new Dictionary<object, object>();
}
public static void AssociateParent(this object child, object parent)
{
Items[child] = parent;
}
public static object GetParent(this object child)
{
if (Items.ContainsKey(child)) return Items[child];
return null;
}
}

Manipulating collection from the inside

I have a collection of panels which are highlighted when user clicks on them. I want to force them to behave as a set of radio buttons so only the one that is clicked on is highlighted and others aren't.
I guess that there must be a way to manipulate whole collection (set property to false) from the inside, because the event is triggered by one item from the collection. Is there a way for the one item to manipulate whole collection? This is such a common feature in applications so I guess there must be a pattern how to do it properly. Thanks.
You may store collection of your panels and handle required functionality as in following code snippet:
List<Panel> Panels;
private void Initialization()
{
Panels = new List<Panel>();
Panels.Add(pnl1);
Panels.Add(pnl2);
//add all your panels into collection
foreach(Panel Item in this.Panels)
{
//add handle to panel on click event
Item.Click += OnPanelClick;
}
}
private void OnPanelClick(object sender, EventArgs e)
{
foreach(Panel Item in this.Panels)
{
//remove highlight from your panels, real property should have other name than Panel.HighlightEnabled
Item.HighlightEnabled = false;
}
((Panel)sender).HighlightEnabled = true; //add highlight to Panel which invoked Click event
Application.DoEvents(); //ensure that graphics redraw is completed immediately
}
private void AddNewPanelIntoLocalCollection(Panel panel)
{
//here you can add new items to collection during program lifecycle
panel.Click += OnPanelClick;
this.Panels.Add(panel);
}
This is how I do it
public class SelectOne : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private bool isSelected = false;
private HashSet<SelectOne> selecteOnes = null;
public bool IsSelected
{
get { return isSelected; }
set
{
if (isSelected == value) return;
if (isSelected && selecteOnes != null)
{
foreach (SelectOne so in selecteOnes)
{
if (so == this) continue;
so.IsSelected = false;
}
}
NotifyPropertyChanged("IsSelected");
}
}
public SelectOne() { }
public SelectOne(bool IsSelected) { isSelected = IsSelected; }
public SelectedOne(bool IsSelected, HashSet<SelectOne> SelecteOnes)
{
isSelected = IsSelected;
selecteOnes = SelecteOnes;
}
}
Eventually I did find a way to do this properly with only one delegate.
In class A I have a collection of objects B
List<B> b = new List<B>
class B, needs to have an unique ID and delegete for void metod with Id parameter
delegate void DeleteItemDelegate(int id);
class B
{
public int ID {get; set;}
public DeleteItemDeleate deleteThis {set; get;}
}
class A has a metod like this:
public void RemoveItem(int id)
{
for (int x = 0; x < b.Count; x++)
{
if (b[x].id == id)
{
b.RemoveAt(x);
}
}
}
when adding a new B object into List just add metod RemoveItem to B.deleteThis delegate
B bObject = new B();
bObject.deleteThis = RemoveItem;
b.Add(bObject);
Now all you need to do is add DeleteMe metod in B class
void DeleteMe()
{
// and call local delegate - pointing to metod which actually can manipulate the collection
deleteThis(id);
}

C# strange Double ObservableCollection behaviour

Maybe I don't understand the ObservableCollection well enough. But as far as I knew it was similar to a normal list, but with event triggers so that you can react to changes.
So I have this Windows store app. And in this application I have a main BusinessModel class which is the main source for all data in my client application. This data will be updated when the server has made some changes elsewhere. In the future I'd like to have this class update the ViewModels for specific data updates etc.
So I also have a ViewModel class which contains, at least in my PoC's so far, a copy of that list (also in the near future this list will have an enriched version of the list).
Since it's a copy they should be both separate instances and have their own separate items.
However when I update the copy in the ViewModel, the BusinessModel version changes with it.
And vice versa.
I can't seem to figure out why this is happening. Underneath you will find the classes and their functions:
//the BusinessModel Class
public class ModelStuff : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _modelStuff;
public ObservableCollection<DataObject> modelStuff
{
get
{
return _modelStuff;
}
set
{
_modelStuff = value;
NotifyPropertyChanged("modelStuff");
}
}
private static ModelStuff businessModel;
public static ModelStuff BusinessModel
{
get
{
if (businessModel == null)
{
businessModel = new ModelStuff();
}
return businessModel;
}
}
public ModelStuff()
{
modelStuff = new ObservableCollection<DataObject>();
modelStuff.Add(new DataObject(0));
modelStuff.Add(new DataObject(1));
modelStuff.Add(new DataObject(2));
modelStuff.Add(new DataObject(3));
modelStuff.Add(new DataObject(4));
modelStuff.Add(new DataObject(5));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the ViewModel class
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _visibleStuff;
public ObservableCollection<DataObject> visibleStuff
{
get
{
return _visibleStuff;
}
set
{
_visibleStuff = value;
NotifyPropertyChanged("visibleStuff");
}
}
private static ViewModel tvm;
public static ViewModel TVM
{
get
{
if (tvm == null)
{
tvm = new ViewModel();
}
return tvm;
}
}
public ViewModel()
{
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the TestObjects
public class DataObject
{
public int testNumber { get; set; }
public String testStr { get; set; }
public DataObject(int i)
{
testNumber = i;
testStr = "testje";
}
}
//A randomly placed button invokes this function when clicked.
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
ViewModel.TVM.visibleStuff[0].testNumber = 100;
int i1 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
//Second version but vice versa
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ViewModel.TVM.visibleStuff[0].testNumber;
ModelStuff.BusinessModel.modelStuff[0].testNumber = 100;
int i1 = ViewModel.TVM.visibleStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
Where has my reasoning gone wrong?
Why is this happening?
And more importantly, how can I prevent this behaviour?
As far as I can see, your line of code:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
is not making a copy of the underlying objects at all. It is adding the same DataObjects from the original list to a new ObservableCollection.
You need to clone the DataObjects individually and add them to the new collection. Something like this should do it:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber).Select(i => new DataObject(i.testNumber)));

Categories