Reputation: 121
Edit: I marked an answer, since it did bring me on the right track. The only issue left seems to be a design thing and according to the boardrules should be asked as a seperate question. See my followup question here: https://stackoverflow.com/questions/19744475/design-issue-how-to-approach-this-specific-task Thanks for your help here!
I am trying to bind a list of custom objects to a DataGrid. Straight binding seems easy enough, but i need to specify some complex formulas for some extra fields that do not directly show up in my class. Also i want to be able to EDIT the data in the Grid and get updates on related fields. Let me show you an example, because it is really hard to explain. I will simplify it to rooms with items. Each item can be red and blue.
My Class looks like this:
public class room
{
public string strRoomName { set; get; }
public string strItemname { set; get; }
public int intRedItem { set; get; }
public int intBlueItem { set; get; }
}
Now if i use dataTable.ItemSource = myList; i get something like this:
nr. | room | name | red | blue
1. living room, ball, 2, 1
2. sleeping room, bunny, 4, 1
3. living room, chair, 3, 2
4. kitchen, ball, 4, 7
5. garage, chair, 1, 4
Now for the complex part i need help with. I want every item to be the same number, red and blue. And because this does not hold true i want to see the "inbalance" per room AND globally like this:
nr. | room | name | red | blue | missing | global red | global blue | global missing
1. living room, ball, 2, 1, 1 blue, 6, 7, 1 red
2. sleeping room, bunny, 4, 1, 3 blue, 4, 1, 3 blue
3. living room, chair, 3, 2, 1 blue, 4, 6, 2 red
4. kitchen, ball, 4, 7, 3 red, 6, 7, 1 red
5. garage, chair, 1, 4, 3 red, 4, 6, 2 red
As you can see this smeels like excel formulas, i am unsure how to handle this in c# code however. You can also see i need to use data in the same row, but also get data from other rows that match one propertiy (the items name).
Also if i change the blue value=1 in line 1 to value=2, i want line 1 to read like this:
1. living room, ball, 2, 2, even, 6, 8, 2 red
and of corse line 4 needs to change to:
4. kitchen, ball, 4, 7, 3 red, 6, 8, 2 red
Edit: After reading trough your answers and testing my skills to implement your hints, i now have the following code as my class:
public class RoomList : ObservableCollection<room>
{
public RoomList() : base()
{
Add(new room() { strRoomName = "living room", strItemname = "ball", intRedItem = 2, intBlueItem = 1 });
Add(new room() { strRoomName = "sleeping room", strItemname = "bunny", intRedItem = 4, intBlueItem = 1 });
Add(new room() { strRoomName = "living room", strItemname = "chair", intRedItem = 3, intBlueItem = 2 });
Add(new room() { strRoomName = "kitchen", strItemname = "ball", intRedItem = 4, intBlueItem = 7 });
Add(new room() { strRoomName = "garage", strItemname = "chair", intRedItem = 1, intBlueItem = 4 });
}
}
//rooms
public class room : INotifyPropertyChanged
{
public string strRoomName { set; get; }
public string strItemname { set; get; }
private int _intRed = 0;
private int _intBlue = 0;
public int intRedItem
{
get { return _intRed; }
set
{
_intRed = value;
NotifyPropertyChanged("intRedItem", "strMissing");
}
}
public int intBlueItem
{
get { return _intBlue; }
set
{
_intBlue = value;
NotifyPropertyChanged("intBlueItem", "strMissing");
}
}
public string strMissing
{
get
{
int missingCount = intRedItem - intBlueItem;
return missingCount == 0 ? "Even" : missingCount.ToString();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (string propertyName in propertyNames)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Edit 1: I got the "missing" field working right away, thanks alot for that tip. It really was as easy as i imagined and will be of great use for future projects.
Edit2: Fixed the StackOverflow-Error with adding private dummy variables in my class, see code above.
Edit3: Also fixed the ressource, even tho i swear i used exactly the same code yesterday, but now it is working. Oh well.
Only one real issue left:
How do i set a propperty based on OTHER objects properties. The "globalRed" property would need to loop over my list, or the collection, but both don't seem to be an option. At least not like i usually do this in code. "listRooms" does not show up in a properties getter. Also this would need to loop again after new objects get added. I guess then otify method would handle this as well, but how do i loop in the first place?
And one more question for better understanding:
Can anyone explain, in simple words, why the above collection is preferable over the list- approach? For all i was able to test they behave identically.
//the list-Way
datagridRooms.ItemsSource = listRooms.Where(x=>x.strRoomName == "living room");
//indentical with the collection-way...
datagridRooms.ItemsSource = new RoomList().Where(x=>x.strRoomName== "living room");
Upvotes: 1
Views: 294
Reputation: 69985
If you're going to use WPF, then you're going to need to implement the INotifyPropertyChanged
Interface on your data type classes... your Room
class. You should also use the ObservableCollection<T>
Class for your collections. This enables your UI to update as the data is changed and vice versa.
Now for your 'Excel
formula' fields, you can just create a property for each. You might have to adjust the 'formula' to suit your needs more accurately, but to use your Missing
field as an example:
public string MissingCount
{
get
{
int missingCount = intRedItem - intBlueItem;
return missingCount == 0 ? "Even" : missingCount.ToString();
}
}
To make this update in the UI, we need to alert the INotifyPropertyChanged.PropertyChanged
event of the change, but because we don't set this property (and that is when we should notify), we have to send notification from the properties that are used in MissingCount
:
public int RedItemCount
{
get { return redItemCount; }
set
{
redItemCount = value;
NotifyPropertyChanged("RedItemCount", "MissingCount");
}
}
public int BlueItemCount
{
get { return blueItemCount; }
set
{
blueItemCount = value;
NotifyPropertyChanged("BlueItemCount", "MissingCount");
}
}
public void NotifyPropertyChanged(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (string propertyName in propertyNames)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Now whenever your RedItemCount
or your BlueItemCount
property values change, the UI will update those values and the MissingCount
value. If you just follow this example for each of your fields, you should be able to get what you want.
UPDATE >>>
Ok, so you seem to be missing one vital thing. In MVVM, we'd call it a view model. Basically, it's a class that holds all of the properties containing the objects that you want to display. So, while you almost have that already, you'll need at least one more property:
public Room SelectedRoom
{
get { return selectedRoom; }
set
{
selectedRoom = value;
NotifyPropertyChanged("SelectedRoom");
// Calculate new values here
}
}
Of course, you'll still need your collection property:
public RoomList RoomList
{
get { return roomList; }
set { roomList = value; NotifyPropertyChanged("RoomList"); }
}
With these properties, you can Bind
the collection with the ItemsSoucre
property and also Bind
directly with the SelectedItem
of a collection control (or the CurrentItem
property of a DataGrid
... for example:
<DataGrid ItemsSource="{Binding RoomList}" CurrentItem="{Binding SelectedRoom}" ... />
Now whenever a new item is selected/focused in the UI, program execution will jump to the SelectedRoom
setter in the view model where you have access to both the whole collection and the selected item and you can calculate anything that you need to.
You will need to set an instance of this view model class as the DataContext
for the UserControl
/Window
, or 'view'.
Upvotes: 1
Reputation: 31721
extra fields that do not directly show up in my class.
If you can't add more properties to your existing class, one of the best things you can do is create a Partial class off of the primary class you mentioned. There you can add the logic of the other columns as properties and put in business logic within the property getters/setters to handle the excel type operations mentioned.
If a partial class is not available, then create a class which inherits the original class and implements the extended properties needed. As long as the original class isn't sealed preventing that.
Regardless once done you can use the new modified class to be displayed in the grid.
Also i want to be able to EDIT the data in the Grid and get updates on related fields.
You will need to make your class adhere to INotifyPropertyChanged. That will allow for changes to occur and be presented to the screen without any direct involvement from you.
a better way to structure my class.
Do a basic MVVM (Model, View, View-Model approach). It is simply a way to organize your data away from the screen logic.
The view is the page which holds the grid and handles events related to to the GUI. The ViewModel is in charge of getting data and frankly just holding data to be bound to by the View. That is where you should put your list of items, and put it into an Observable Collection which helps when you and or remove items from the list it sends notifications to the GUI (and you if you want to subscribe to such events). The M is models and that is just your class you mentioned earlier. I provide a basic example showing the VM and View on my blog article entitled Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding.
Get into the habit of defining class instances in the terms of C# interfaces. If one looks at an interface as a contract to do a specific task, that in itself provides a consistency of development which leads to very extensible code. With an interface one can bring together disparate objects into one list and process them in a consistent manner. Very powerful in terms of thinking of your code in more than just classes and instances, but at a more higher level of comprehension which leads to more features being able to be added and making the code testable (see unit testing in Visual Studio). It takes more work upfront to use interfaces, but it pays off in the future and is something to think about as you learn C# going forward.
Upvotes: 1