I am using Reactive DynamicData to sort my collections and update the WPF UI. I can apply a sort easy. The problem is how do I get the items to sort back to their original order? What do I send in the DoSort() so it will return to the unsorted original order?
IObservable<IChangeSet<T>> set;
Subject<IComparer<T>> _sorting;
Subject<Unit> _resorting;
ReadOnlyObservableCollection<T> _itemset;
private SortExpressionComparer<T> _currentSort;
private void DoSort(IComparer<T> sort)
{
_sorting?.OnNext(sort);
//Resort();
}
public void Resort()
{
_resorting?.OnNext(Unit.Default);
}
public ReadOnlyObservableCollection<T> ItemSet()
{
if (_itemset is null)
{
_sorting = new Subject<IComparer<T>>();
_resorting = new Subject<Unit>();
set.Sort(_sorting, resort: _resorting).ObserveOnDispatcher().Bind(out var list).Subscribe();
_itemset = list;
DefaultSort();
}
return _itemset;
}
The IChangeSet<T> that you sort doesn't store the previous ordering of the items somewhere so there is no way to get back the "original" order unless you know how the set was originally sorted and you sort it again using the same IComparer<T>.
What do I send in the DoSort() so it will return to the unsorted original order?
An IComparer<T> that sorts the items in the original order basically.
Related
How do I restrict one random prefab to be used only once but placed randomly with a bunch of prefabs of arrays on top of other object?
using System.Collections.Generic;
using UnityEngine;
public class LevelRoomsScript : MonoBehaviour
{
[SerializeField]
private GameObject[] memberWoodArray = null;
[SerializeField]
private GameObject[] memberRoomPrefabArray = null;
void Start()
{
foreach (GameObject localWood in memberWoodArray)
{
int localNumRoomPrefab = memberRoomPrefabArray.Length;
int localRoomIndex = Random.Range(0, localNumRoomPrefab);
GameObject localRoomPrefab = memberRoomPrefabArray[localRoomIndex];
Instantiate(localRoomPrefab, localWood.transform.position, Quaternion.identity);
}
}
}
The way I understand your question is that you want to instantiate each element in memberRoomPrefabArray at most once.
You could create a temporary list that is a copy of memberRoomPrefabArray and remove each element that is instantiated before the next loop cycle.
void Start()
{
List<GameObject> temp = new List<GameObject>(memberRoomPrefabArray);
foreach (GameObject localWood in memberWoodArray)
{
int localRoomIndex = Random.Range(0, temp.Count);
Instantiate(temp[localRoomIndex], localWood.transform.position, Quaternion.identity);
temp.RemoveAt(localRoomIndex);
}
}
You might want to add some checks like if (temp.Count == 0) { break; } if it's possible for memberRoomPrefabArray to be shorter than memberWoodArray.
Edit: Changed Random.Range(0, temp.Count - 1) to Random.Range(0, temp.Count) since, apparently, it's only maximally inclusive with floats and not integers.
You rather want to "shuffle" the array once and then iterate the shuffled array e.g. using Linq OrderBy and using Random.value as order like
using System.Linq;
...
void Start()
{
if(memberRoomPrefabArray.Length < memberWoodArray.Length)
{
Debug.LogError($"Not enough prefabs available for {memberWoodArray.Length} unique spawns!", this);
return;
}
// as the method names suggest this will be a randomized array of the available prefabs
var shuffledPrefabs = memberRoomPrefabArray.OrderBy(m => Random.value).ToArray();
for (var i = 0; i < memberWoodArray.Length; i++)
{
// Since the array is already shuffled we can now go by consecutive order
Instantiate(suffledPrefabs[i], memberWoodArray[i].transform.position, Quaternion.identity);
}
}
I've found a lot of information on how to sort a List, but none on how to sort a List>. Specifically I have a map of key values like:
"Name": "John",
"Savings": 300
I have a list of a bunch of objects like this and need to sort on "Savings". Any ideas?
Have you looked into the Comparable interface? You will need to wrap the Map<String,String> inside a new class that has a comparable() method in order to make it sortable. More info here: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_comparable.htm
public class MapWrapper implements Comparable {
public Map<String,String> record;
public MapWrapper(Map<String,String> record) {
this.record = record;
}
public Integer compareTo(Object compareTo) {
MapWrapper compareToMap = (MapWrapper)compareTo;
if (record.get('Savings') == compareToMap.record.get('Savings')) return 0;
if (record.get('Savings') > compareToMap.record.get('Savings')) return 1;
return -1;
}
}
Your list will contain MapWrapper objects instead of Map<String,String>. You can then sort it using List.sort():
List<MapWrapper> myList = new MapWrapper[]{};
myList.add(new MapWrapper(new Map<String,String>{'Name'=>'John','Savings'=>'300'}));
myList.add(new MapWrapper(new Map<String,String>{'Name'=>'David','Savings'=>'100'}));
System.debug(myList); //Unsorted
myList.sort();
System.debug(myList); //Sorted
If you need to keep your structure as a List<Map<String,String>> without the wrapper, you can still do it but you will need to implement the sort algorithm yourself. The easiest way to do it is using bubble sort.
Using datagridview bound to BindingSource control bound to a LINQ to SQL class, I wonder how to position the bindingSource to a specific record, that is, when I type a Product name in a textbox, the bindingsource should move to that specific product. Here is my code:
In my form FrmFind:
NorthwindDataContext dc;
private void FrmFind_Load(object sender, EventArgs e)
{
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list.OrderBy(o => o.ProductName);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
int index = productBindingSource.Find("ProductName", tb.Text);
if (index >= 0)
{
productBindingSource.Position = index;
}
}
In the program class:
public class FindAbleBindingList<T> : BindingList<T>
{
public FindAbleBindingList()
: base()
{
}
public FindAbleBindingList(List<T> list)
: base(list)
{
}
protected override int FindCore(PropertyDescriptor property, object key)
{
for (int i = 0; i < Count; i++)
{
T item = this[i];
//if (property.GetValue(item).Equals(key))
if (property.GetValue(item).ToString().StartsWith(key.ToString()))
{
return i;
}
}
return -1; // Not found
}
}
How can I implement the find method to make it work?
You can combine the BindingSource.Find() method with the Position property.
For example, if you have something like this in your TextBox changed event handler:
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
int index = bs.Find("Product", tb.Text);
if (index >= 0)
{
bs.Position = index;
}
}
This of course will depend on a lot of things like the particular implementation of the Find method the data source for the binding source has.
In a question you asked a little while ago I gave you an implementation for Find which worked with full matches. Below is a slightly different implementation that will look at the start of the property being inspected:
protected override int FindCore(PropertyDescriptor property, object key)
{
// Simple iteration:
for (int i = 0; i < Count; i++)
{
T item = this[i];
if (property.GetValue(item).ToString().StartsWith(key.ToString()))
{
return i;
}
}
return -1; // Not found
}
Do note that the above method is case sensitive - you can change StartsWith to be case insensitive if you need.
One key thing to note about the way .Net works is that the actual type of an object is not sufficient all the time - the declared type is what consuming code knows about.
This is the reason why you get a NotSupported exception when calling the Find method, even though your BindingList implementation has a Find method - the code that receives this binding list doesn't know about the Find.
The reason for that is in these lines of code:
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list.OrderBy(o => o.ProductName);
When you set the data source for the binding source you include the extension method OrderBy - Checking this shows that it returns IOrderedEnumerable, an interface described here on MSDN. Note that this interface has no Find method, so even though the underlying FindableBindingList<T> supports Find the binding source doesn't know about it.
There are several solutions (the best is in my opinion to extend your FindableBindingList to also support sorting and sort the list) but the quickest for your current code is to sort earlier like so:
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).OrderBy(p => p.ProductName).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list;
In WinForms there are no entirely out of the box solutions for the things you are trying to do - they all need a little bit of custom code that you need to put together to match just your own requirements.
I took a different approach. I figured, programmatically, every record must be checked until a match is found, so I just iterated using the MoveNext method until I found a match. Unsure if the starting position would be the First record or not, so I used the MoveFirst method to ensure that is was.
There is one assumption, and that is that what you are searching for is unique in that column. In my case, I was looking to match an Identity integer.
int seekID;
this.EntityTableBindingSource.MoveFirst();
if (seekID > 0)
{
foreach (EntityTable sd in EntityTableBindingSource)
{
if (sd.ID != seekID)
{
this.t_EntityTableBindingSource.MoveNext();
}
else
{
break;
}
}
}
I didn't really care for either answer provided. Here is what I came up with for my problem:
// Create a list of items in the BindingSource and use labda to find your row:
var QuickAccessCode = customerListBindingSource.List.OfType<CustomerList>()
.ToList().Find(f => f.QuickAccessCode == txtQAC.Text);
// Then use indexOf to find the object in your bindingSource:
var pos = customerListBindingSource.IndexOf(QuickAccessCode);
if (pos < 0)
{
MessageBox.Show("Could not find " + txtQAC.Text);
}
else
{
mainFrm.customerListBindingSource.Position = pos;
}
In an MVVM environment, I have a ListCollectionView bound to an ObservableCollection. My Foo objects have a IsDefault property to them, and my requirement is to have that item first in the list, and the rest should be alpha-sorted.
So this code only sorts the whole list, obviously:
_list = new ListCollectionView(Model.Data);
_list.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
Not sure how to make sure that item #3 (for example, which has IsDefault=true) be at the top of the list, and the rest (that have IsDefault=false) be alpha sorted.
Is this a case to use _list.CustomSort and implement IComparer in some way?
Yes, this is exactly the case where you need to use ListCollectionView.CustomSort. Custom sorting is mutually exclusive with using SortDescriptions; the documentation for the former is explicit about this:
Setting this property clears a previously set SortDescriptions value.
So what you need to do is define an IComparer and use it to sort the view:
class CustomComparer : IComparer
{
public int Compare (object lhs, object rhs)
{
// missing: checks for null, casting to Model.Data, etc etc
if (lhsModelData.IsDefault && rhsModelData.IsDefault) {
return lhsModelData.Name.CompareTo(rhsModelData.Name);
}
else if (lhsModelData.IsDefault) {
return -1;
}
else if (rhsModelData.IsDefault) {
return 1;
}
else {
return lhsModelData.Name.CompareTo(rhsModelData.Name);
}
}
}
I have a data set whose elements are displayed as rows in a DataGrid. The sort order for the rows changes in response to external events.
My initial thought was to store the rows as an ObservableCollection and resort the collection after updates. However I ran into two problems:
1) the ObservableCollection does not have a Sort() method
2) if I try to sort the elements myself, I get an exception whenever I try to assign an element to a new position, for example in a swap function like
class MyCollection : ObservableCollection<T>
{
void swap( int i, int j )
{
T tmp = this[i];
this[i] = this[j]; // THROWS A NOT SUPPORTED EXCEPTION
this[j] = tmp;
}
}
So the question is ... how to populate a DataGrid whose row order needs to update dynamically?
I did finally get one answer working, I'll describe it below.
I got this to work by implementing INotifyCollectionChanged explicitly (instead of using ObservableCollection). Furthermore, I found that using the Update action resulted in the same "not supported" error, but that I could use the Add and Remove actions. So my swap function ends up looking like this:
class MyCollection<T> : List<T>, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void swap( int i, int j )
{
T a = this[i];
T b = this[j];
// swap my own internal data storage
this[i] = b;
this[j] = a;
// and also let my CollectionChanged listener know that I have done so.
if( CollectionChanged != null )
{
NotifyCollectionChangedEventArgs arg;
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, a, i );
CollectionChanged( this, arg );
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, b, i );
CollectionChanged( this, arg );
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, b, j );
CollectionChanged( this, arg );
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, a, j );
CollectionChanged( this, arg );
}
}
}
The dynamic changes are fairly local, so fortunately using a slower handwritten sort in response to changes is working OK for me. In other words, when updates arrive, I invoke another member function (in the same collection) that looks something like this:
public void ProcessUpdates( List<T> updateList )
{
// use the contents of updateList to modify my internal store
// ...
// and now resort myself
sort();
}
private void sort()
{
// implement your favorite stable sorting algorithm here, calling
// swap() whenever you swap two elements.
// (this is an intentionally facetious sorting algorithm, because I
// don't want to get into the long and irrelevant details of my own
// data storage.)
while( i_am_not_sorted() )
{
int i = random_index();
int j = random_index();
if( out_of_order(i,j) )
{
// modify my internal data structure and
// also let my CollectionChanged listener know that I have done so
swap( i, j );
}
}
}
Don't forget that it's also necessary to fire an "Add" notification when adding elements to the collection! I sort the initial list and then add in sorted order, which lets me use a more efficient library sort when I first populate the data.