Toggle distinct and actual items in list - c#

FileRecord is the observable collection that is being binded with my wpf datagrid in MVVM model.
I have one checkbox for each column above my datagrid. Checkbox name is "SelectUnique--Columnname--". When I click those checkboxes it should show unique values for the column in my grid.
When I click unique check box for TId, I do below logic
var grpd = FileRecord.GroupBy(item => item.TID).Select(grp => grp.First());
FileRecord= new ObservableCollection<FileData>(grpd); // will refresh the grid.
Then again When I click unique check box for CId, I do below logic
var grpd = FileRecord.GroupBy(item => item.CID).Select(grp => grp.First());
FileRecord= new ObservableCollection<FileData>(grpd);// will refresh the grid.
and so on. In this case, for example, if I do unique selection for all my columns, then again If I want to deselect the checkbox randomly(not in the order I selected unique checkboxes) I would like to undo what I have done for that particular column. For example, if I unselect CID unique check box, then the grid should so proper result.
How to acheive this? Please help.

When I want to filter a collection like this I have a property like this:
public IEnumerable<FileData> FilteredFiles
{
get
{
if (Unique)
{
return Files.GroupBy(item => item.TID).Select(grp => grp.First());
}
else
{
return Files.GroupBy(item => item.CID).Select(grp => grp.First());
}
}
}
public ObservableCollection<FileData> Files
{
get; set;
}
public bool Unique
{
get
{
return unique;
}
set
{
unique = value;
RaisePropertyChanged("FilteredFiles");
}
}
Bind to FilteredFiles and when you add/remove from the collection just call RaisePropertyChanged("FilteredFiles") to notify the UI.

You should have a reference of the original collection somewhere, and do all calculations over that one.
For instance, you could have a single method that gets called whenever a CheckBox is checked or unchecked, and have that method filter/group the original collection.
// Simplified properties
private IEnumerable<FileData> FileRecordCollection;
public ObservableCollection<FileData> FileRecord { get; set; }
// Event handlers for the CheckBoxes
private void TID_CheckBox_Checked(object sender, RoutedEventArgs e)
{
UpdateFileRecord();
}
private void TID_CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
UpdateFileRecord();
}
// etc.
// Method that updates FileRecord
private void UpdateFileRecord()
{
IEnumerable<FileData> groupedCollection = FileRecordCollection;
if (TID_CheckBox.IsChecked)
groupedCollection = groupedCollection.GroupBy(item => item.TID).Select(grp => grp.First());
if (CID_CheckBox.IsChecked)
groupedCollection = groupedCollection.GroupBy(item => item.CID).Select(grp => grp.First());
// etc.
FileRecord = new ObservableCollection<FileData>(groupedCollection);
}
This isn't exactly optimal, but I can't think of something better (performance-wise) right now.

Related

C#, Xamarin Forms - ObservableCollection of custom Object updates but does not fire property changed events

I have a collection that gets populated with produce. Once the collection is populated, a BindableLayout/DataTemplate bound to a StackLayout will display the items and the user will be prompted with the option to change the Price property of a stock item by typing into an Entry.
A user can type into the provided Entry box to change Price property of each StockInfo object in the collection, and the change WILL SUCCESSFULLY be applied to the Observable Collection, BUT it WILL NOT fire the setter/property changed event of the Observable Collection.
I need the property changed event to fire so that I can effectively execute other parts of my code, but since it won't the fire setter or property changed of the collection, it never gets the chance to tell other parts of my code to do things.
namespace Test
{
public class Testing : BaseContentPage, INotifyPropertyChanged
{
public class StockInfo : BaseContentPage, INotifyPropertyChanged
{
private string description;
public string Description
{
get => description;
set
{
description = value;
OnPropertyChanged();
}
}
private int price;
public int Price
{
get => price;
set
{
price = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<StockInfo> stockItems = new ObservableCollection<StockInfo>();
public ObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
OnPropertyChanged("SumPrices");
}
}
public double SumPrices
{
get
{
return StockItems.Sum(p => p.Price);
}
}
DataTemplate StockTemplate = new DataTemplate(() =>
{
return new StackLayout
{
Orientation = StackOrientation.Horizontal,
Children =
{
new Entry
{
}.Bind(Entry.TextProperty, path: "Description")
,
new Entry
{
Keyboard = Keyboard.Numeric
}.Bind(Entry.TextProperty, path: "Price")
}
};
});
public Testing()
{
BindingContext = this;
StockItems.Add(new StockInfo { Description = "Milk", Price = 20 });
StockItems.Add(new StockInfo { Description = "Cheese", Price = 15 });
Content = new StackLayout
{
Children =
{
new StackLayout
{
}.Invoke(layout => BindableLayout.SetItemTemplate(layout, StockTemplate))
.Bind(BindableLayout.ItemsSourceProperty, path: "StockItems")
,
new Label
{
}.Bind(Label.TextProperty, path: "SumPrices")
}
};
}
}
}
If I put a debugger stop line inside the get/set of the "Description" property in the StockInfo class and then type in the Entry, the debugger will pick it up and stop the program for debugging.
But if I put a debugger stop on a line some where in the set/get of the Observable Collection, the program will not stop inside of it.
*** Edits Below ***
I modified the code so that StockInfo now has a property that includes the price of a product. I also added a variable called SumPrices which will return the Sum of Price within StockItems using LINQ. The first time the page loads, the sum is calculated and the result is correct, but if I change the Entry box that the property is bound to for each object, it has no effect and the SumPrices variable never changes.
Ideally, I'd simply like for the Observable Collection to fire its setter/property change events whenever an Object's property within the collection is changed.
New Update Here
You cannot fire the setter of ObservableCollection when a property of an item in this collection has changed. I've searched so many info from the Internet and found a question similar to yours: ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged). Bob Sammers abstract and define a new FullyObservableCollection class and put forward a pretty robust solution, including some of the techniques in other answers. This new class could get notified when a property of item has been changed. I have tested it and worked well.
Simply used it like the following code:
private FullyObservableCollection<StockInfo> stockItems = new FullyObservableCollection<StockInfo>();
public FullyObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
}
}
public Testing ()
{
...
StockItems.ItemPropertyChanged += StockItems_ItemPropertyChanged;
...
}
private void StockItems_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
Another workaround is what i've suggested in my previous answer, which is using TextChanged event handler.
In Datatemplate, add an EventHandler for entry:
new Entry
{
}.Bind(Entry.TextProperty, path: "Description",BindingMode.TwoWay)
.Invoke(entry=>entry.TextChanged+=Entry_TextChanged)
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
For more info, you could refer to Xamarin.Forms - CollectionView sample
Hope it works for you.

How do you update list from changing values in row of selected index of datagridview?

I have a list within a class called vehicle. This contains its own list that is called activity that contains instances of different activities which are also classes. When I add activities to a particular vehicles activity list, it adds a new instance of that activity and refreshes the datagridview to display the list of activities. What I would like to do is click a row in the datagridview and populate some textboxes, then have a button that updates the activity object in the list.
This is how I retrieve the list:
private Vehicle _activeVehicle; //Where _activeVehicle is set prior as a specific instance of a vehicle
var list = _activeVehicle.ActivityList;
Here is the class for my activity that is added to the activity list:
public class Relocate : Activity
{
private string _distance;
private string _type;
private string _name;
private string _cost;
private DateTime _date;
private string _start;
public string Type { get => _type; set => _type = value; }
public string Name { get => _name; set => _name = value; }
public string Cost { get => _cost; set => _cost = value; }
public DateTime Date { get => _date; set => _date = value; }
public string Distance { get => _distance; set => _distance = value; }
public string Start { get => _start; set => _start = value; }
}
When an item in the datagridview is clicked, I have a row click method that adds the values of each cell in that row too some textboxes. Currently I am directly getting the values as shown below, but ideally I would like to select the actual object from the list to populate the textboxes, but Im not sure how to do that based off of a datagridview cell click. Here is how I do it:
DataGridView table = relocationDataGridView;
table.ClearSelection();
DataGridView.HitTestInfo index = table.HitTest(e.X, e.Y);
table.Rows[index.RowIndex].Selected = true;
IndexRelocate = index.RowIndex;
DataGridViewRow dataGrid = table.Rows[index.RowIndex];
switch (index.RowIndex)
{
case -1:
{
MessageBox.Show("This is not a record", "", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
default:
{
relocationCostTextBox.Text = dataGrid.Cells[0].Value.ToString();
relocationStartTextBox.Text = dataGrid.Cells[1].Value.ToString();
relocationTypeTextBox.Text = dataGrid.Cells[2].Value.ToString();
relocationEndTextBox.Text = dataGrid.Cells[3].Value.ToString();
relocationDistanceTextBox.Text = dataGrid.Cells[4].Value.ToString();
relocationDateTimePicker.Value = (DateTime)dataGrid.Cells[5].Value;
break;
}
}
Relocation is the activity in the datagridview, where the class has the attributes of Cost, Start, Type, End, Distance, Date
What I would like to do is update the specific object in the list, where the list belongs to _activeVehicle from clicking the datagridview, entering new details in the textboxes, and clicking an update button, but Im not sure where to start with writing the code for this. The code below populates the textboxes with the current details of the selected row, but would be better if it used that row for reference and pulled the data from the list belonging to _activeVehicle. How might I achieve this?

Searchable dynamic combobox WPF

I'm attempting to create a searchable combobox where the dropdown items (in an ObservableCollection) update as the user text.
For example:
Let's say I have a list of all the cities in the world (10,000+), something which is too large to display in a drodown list. This list (max 100 items) is retrieved through an API which sends a search term within the GET request.
So as the user searches for a specific city, the observable collection is updated to show the related search terms and the data-bound combobox is updated.
When search = "", returns first 100 cities (those beginning with A) from API. Observablecollection/combobox is updated to show.
When search = "London", returns x cities with London in the name from API. Observablecollection/combobox is updated to show.
Currently, I have the following code to update the observable collection when the search value is changed (this works using INotifyPropertyChanged). However when the collection and by association the data-bound combobox is updated, the user's search term is removed.
public ObservableCollection<string> cities { get; set; }
private string searchString;
public string SearchString
{
get
{
return searchString;
}
set
{
searchString = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("SearchString");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
if (name == "SearchString")
{
UpdateCitiesDropdown();
}
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public async void UpdateCitiesDropdown()
{
// Retrieve new cities
List<string> newCities = await GetCities(searchString);
// Save old cities to remove (from observable collection)
List<string> oldCities = cities.ToList<string>();
foreach (string city in oldCities )
{
cities.Remove(city );
}
// Add new
foreach (string city in newCities)
{
cities.Add(city );
}
}
This the current combobox with it's bound data.
<ComboBox ItemsSource="{Binding cities}" Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}" IsEditable="True"/>
Is there any way to update the observable collection and have the combobox updated without removing the user search?
The text which is preselected in the ComboBox is the text of the currently selected item.
In UpdateCitiesDropdown method you are removing all old cities including the selected item. That's the reason why the search is set to blank later.
The solution can be to add SelectedCity property to your view model.
private string selectedCity;
public string SelectedCity
{
get { return selectedCity; }
set
{
selectedCity = value;
OnPropertyChanged("SelectedCity");
}
}
Bind the property to ComboBox's SelectedItem
<ComboBox ItemsSource="{Binding cities}" Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedCity}" IsEditable="True"/>
And modify UpdateCitiesDropdown method not to remove SelectedItem from cities collection
...
List<string> oldCities = cities.Where(x => x != SelectedCity).ToList<string>();
...
foreach (string city in newCities)
{
if (city != SelectedCity)
{
cities.Add(city);
}
}

dynamically generating controls

I have a foreach loop and I need to create a button that allows the user to get the address of a specific location. The issue is when the page is generated, if you click ANY button, they all display the popover.
#foreach (var schedule in _schedules) {
<BSButton Id="popover1" onclick="onclick1">#schedule.Location.NickName</BSButton>
<BSPopover Target="popover1" IsOpen="#IsOpen1" Placement="Placement.Top">
<BSPopoverHeader>#schedule.Location.Name</BSPopoverHeader>
<BSPopoverBody>#schedule.Location.Address</BSPopoverBody>
</BSPopover>
}
code on top of the same page
#code {
bool IsOpen1 { get; set; }
void onclick1(MouseEventArgs e)
{
IsOpen1 = !IsOpen1;
StateHasChanged();
}
}
I am having trouble figuring out how to generate this type of control. I know the problem is the onclick is the same for all the controls. Even if I dynamically change the name in the onclick="#popoverTextId", how do I dynamically create the code in the #code {}
I'll assum Schedule has an Id. Otherwise, improvise something.
<BSButton #onclick="() => onclick1(schedule.Id)"> ... </BSButton>
.... IsOpen="#(schedule.Id == SelectedId)" ...
void onclick1(int scheduleId)
{
SelectedId = scheduleId;
//StateHasChanged();
}

How to Show text in combobox when no item selected like "..Select room..."

How can I show Text in ComboBox when no item selected in Windows Application using LINQ C#
Here is my code how I get all rooms.... in Combobox.
private void LoadRoom()
{
try
{
db = new HotelEntities();
// cmbProvince.Text = "";
var Room = (from u in db.Room
select new { u.RoomId, u.RoomNumber }).ToList();
cmbRoom.Text = ".. Select.."; // This one do not working.
cmbRoom.DisplayMember = "RoomNumber";
cmbRoom.ValueMember = "RoomId";
cmbRoom.DataSource = Room;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Thank you!
If you set the DataSource of a combobox, a currencymanager is used on the background and its position is selected (the first item).
Instead of setting DataSource, try adding the items:
cmbRoom.Items.AddRange(Room);
NB, setting Text as a placeholder will not work if an item is chosen and cleared later, unless you add an extra check (in TextChanged or SelectedIndexChanged)
Fast solution
Create a source class for your ComboItems, with (at least) the properties to Display and the inner value of the property. If you create a generic class you can use it for all your combo boxes.
class ComboDisplay<TSource>
{
public string Display {get; set;}
public TSource Value {get; set;}
}
cmbRoom.DisplayMember = nameof(ComboDisplay.Display);
cmbRoom.ValueMember = nameof(ComboDisplay.Value);
When you create the data source for your combobox, make sure you add a default value. In the example below I assume that you want to select items of type Room in your combo:
IEnumerable<Room> availableRooms = myDbContext.Rooms
.Where(room => room.IsAvailable)
.Select(room => new ComboDisplay<Room>
{
Display = room.Name,
Value = new Room
{
Id = room.Id,
...
},
})
// add a dummy value if nothing is selected
.Concat(new Room[]
{
Display = "Please select a room",
Value = null, // meaning: nothing selected
});
After selection, use comboBox1.SelectedValue to get the selected Room, or null if nothing is selected.
Create a Special Combobox class
If you have to use this regularly, consider creating a generic sub class of ComboBox that can display items of a certain TSource, and will return null if nothing is selected:
class MyComboBox<TSource> : ComboBox
{
public MyComboBox() : base()
{
base.DataSource = this.EmptyList;
base.DisplayMember = nameof(ComboDisplay.Display);
base.ValueMember = nameof(ComboDisplay.Value);
}
private static readonly EmptyItem = new ComboDisplay
{
Display = "Please select a value",
Value = null,
}
Make a property that returns the available combo items. Make sure that the EmptyItem is always in the collection:
public IReadonlyCollection<TSource> ComboItems
{
get {return (IReadOnlyCollection<TSource>)base.DataSource;}
set
{
// TODO: check if the empty element is in your list; if not add it
base.DataSource = value;
}
}
Finally: the function to get the Selected value, or null if nothing is selected:
public TSource SelectedValue
{
get => return (TSource)base.SelectedValue;
set
{
// TODO: check if value is in ComboItems
base.SelectedValue = value;
}
}

Categories