Tsaukpaetra's Bindings to a Grid
-
@TwelveBaud said in The Official Status Thread:
@Tsaukpaetra Why do you have a
List
that's decoupled from yourDictionary
? Why not use aBindingList
so you can more easily track changes? Why not use a customIDataAdapter
to create an appropriateDataSet
from your dictionary, and get actual full change control management? If you're using crappy Windows Forms databinding, why aren't you using crappy Windows Forms databinding?@Tsaukpaetra said in The Official Status Thread:
Because I literally think you're speaking Quidditch to me right now.
It's the simplest (in code representation) I could figure for an object with a unique key. Whatever you just spewed out sounds significantly more than about seven lines of boiled plate code...
Storing an object with a unique key in a dictionary to be able to find it by that key is correct. But using the old Windows Forms data binding machinery like
DataGridView
, which only as an extra can work with bare lists, isn't a good fit. Especially when thatDictionary
isn't the data source you're giving it. I'd say "switch to WPF" whose list/grid controls can much more easily deal with this, but that's likely not an option. Thus, in order to make data binding work for you, you're going to have to do some work.One thing that may help is flipping things around: use a
List
orBindingList
to hold the objects, then use Linq'sToLookup()
to get a sort-of-but-not-really dictionary. UnlikeToDictionary()
,ToLookup()
doesn't reify or copy the original data source, so the lookup will stay in sync with the changes that get made to the list.If that is not possible, you can create an
IDataAdapter
. This is whatDataGridView
s are actually designed to work with.DataSet
s keep track of which rows are added and removed and hold onto all the state information from them, so you're able to find whatever key you're using for your dictionary later. While it's intended to be part of an ADO.NET database provider, at the level ofIDataAdapter
it's useful for other types of backing stores as well. ImplementFill()
to convert your dictionary to aDataSet
, and implementUpdate()
to get a list of changes to make back to the dictionary.If that is not possible, as a last resort you can use a
BindingList
instead of aList
. Adding an event handler forListChanged
will at least let you see the numerical index of the underlying list where the changes have been made as they're being made, and depending on how you implemented the conversion from a dictionary to a list that information may be helpful for updating the dictionary.
-
@TwelveBaud said in Tsaukpaetra's Bindings to a Grid:
how you implemented the conversion from a dictionary to a list
Literally just added the dictionary's key type as a property on a subclasses object. Named it ID.
-
Unless there's a bug you can just create a DataView and listen to its events to manage the sync to the 'real' data source, if you don't want to write the data adapter. It's a while since I had to use this control but I think that's what I did a while back and it worked ok.
-
@bobjanova said in Tsaukpaetra's Bindings to a Grid:
Unless there's a bug you can just create a DataView and listen to its events to manage the sync to the 'real' data source, if you don't want to write the data adapter. It's a while since I had to use this control but I think that's what I did a while back and it worked ok.
Yeah, I'm listening to a combination of events from the grid and its data source thing, four in total.
-
@Tsaukpaetra said in The Official Status Thread:
@GOG said in The Official Status Thread:
dead-simple
That's a whole lot of shit to do when all I want is a user-editable grid of stuff and a way to get back the edited data when the user clicks a button....
My current flow is:
- Get the dictionary and transform it into a list
- Shove the list into the dataSourceThingamabob
- Let the user fuck with it
- Grab the list and convert it back into the dictionary
- Profit.
It took about ten minutes of work, after figuring out the wackiness with how it adds new rows but doesn't notify that it's deleting the added row.
@Tsaukpaetra said in The Official Status Thread:
@GOG said in The Official Status Thread:
Have all your commands
That's the crux: There is no command. It's "Load" and "Save". There are no other functions that modify the data at all.
Okay, now I am confused: if the user is fucking with the list, then - conceptually, at least - they are executing commands. Moreover, surely there is something you are saving - modifications to data (adding, changing, deleting rows), I am presuming.
-
@GOG said in Tsaukpaetra's Bindings to a Grid:
@Tsaukpaetra said in The Official Status Thread:
@GOG said in The Official Status Thread:
dead-simple
That's a whole lot of shit to do when all I want is a user-editable grid of stuff and a way to get back the edited data when the user clicks a button....
My current flow is:
- Get the dictionary and transform it into a list
- Shove the list into the dataSourceThingamabob
- Let the user fuck with it
- Grab the list and convert it back into the dictionary
- Profit.
It took about ten minutes of work, after figuring out the wackiness with how it adds new rows but doesn't notify that it's deleting the added row.
@Tsaukpaetra said in The Official Status Thread:
@GOG said in The Official Status Thread:
Have all your commands
That's the crux: There is no command. It's "Load" and "Save". There are no other functions that modify the data at all.
Okay, now I am confused: if the user is fucking with the list, then - conceptually, at least - they are executing commands. Moreover, surely there is something you are saving - modifications to data (adding, changing, deleting rows), I am presuming.
They're interacting with the DataGridView control; I don't need to execute or handle any of the myriad of things that control does, except notice when it wants a new item, user has deleted an item, or user has changed an item; but those aren't commands that I'm implementing; I don't need to write the code that controls what happens when the user double-clicks a cell, for example.
And for those that I do attach an event handler to, they're three lines each (except one).
All that boilerplate code you were talking about writing I don't need to write, no interfaces to implement, no adapters to scaffold, no views to model.
-
@Tsaukpaetra I still fail to see how it's any different in WPF. Writing an
Execute
delegate is exactly the same amount of work as writing an event handler. Only difference is, you're not jamming noodles in the code behind your view, which tends to accumulate all sorts of random cruft as a result.
-
@GOG said in Tsaukpaetra's Bindings to a Grid:
Only difference is, you're not jamming noodles in the code behind your view
This phrase. I do not think it means what you think it means.
Please explain the difference.
-
@Tsaukpaetra The difference is that you can have all your code in a reusable class (with all the object-oriented goodness that entails), or you can add your functionality ad-hoc to the code-behind (add a noodle and JAM IT!)
-
@GOG said in Tsaukpaetra's Bindings to a Grid:
in a reusable class
But I literally don't need that at all... This demo project will not be used anywhere else, so why bother?
And if it was so reusable, why is it not part of the base framework to begin with?
-
@Tsaukpaetra Because it's your code.
-
@GOG said in Tsaukpaetra's Bindings to a Grid:
@Tsaukpaetra Because it's your code.
Exactly. My code doesn't need to be pedagogically beautiful. I don't (contrary to some entities' perceptions) masturbate to pretty code.
-
@Tsaukpaetra I have nothing to say to that...
-
@GOG said in Tsaukpaetra's Bindings to a Grid:
(with all the object-oriented goodness that entails)
For all the hype of OOP, having used it professionally for 15+ years, the best practices I've found are: plain old data objects for data, avoid inheritance in favor of composition wherever possible, and break things apart into the smallest possible units.
-
@GOG said in Tsaukpaetra's Bindings to a Grid:
@Tsaukpaetra I have nothing to say to that...
I mean, you still haven't really convinced me the merits of writing reusable wrappers for wrappers for classes for views for this one throwaway grid over just using the base functionality. Maybe later today I'll anonymize my code and ask what your equivalent might look like for comparison, because I'm just not seeing it...
-
@error said in Tsaukpaetra's Bindings to a Grid:
@GOG said in Tsaukpaetra's Bindings to a Grid:
(with all the object-oriented goodness that entails)
For all the hype of OOP, having used it professionally for 15+ years, the best practices I've found are: plain old data objects for data, avoid inheritance in favor of composition wherever possible, and break things apart into the smallest possible units.
IDK, I've found a use for just about every single aspect of OOP at some point or another. Sure, I could've done those things differently (and have, in the past, before OOP was really a thing... Windows SDK *shudder*)
-
I'm not saying OOP is bad (though I am specifically saying that big object hierarchies are bad). I'm saying KISS is more important, and sometimes OOP encourages complexity.
-
@error said in Tsaukpaetra's Bindings to a Grid:
sometimes OOP encourages complexity.
Right. In my case, I'm doing this one-off thing. If I was making an application full of datagrid views that are basically the same (for some reason), I might make effort to generalize the solution. But since I'm not I feel no need to spend seven times the effort on something that will be used once by two people (maybe).
-
@error I wouldn't say "encourages", exactly. More like: inheritance is one of the first thing you learn in OOP, but you don't usually get told when inheritance is the correct solution and when it isn't.
-
Fun off-topic anecdote:
One of my very first projects as a self-taught programmer (I was 13), I made a Lights Out game (in VB3 [16 bit]). I'd never heard of code reuse before.
Each cell had a unique, hard-coded function to toggle its (hard-coded) neighbors on click. I think it was an 8x8 grid, so I wrote 64 unique functions.
Yes, it was full of copy-pasta errors where some buttons didn't toggle the right lights.
-
@error said in Tsaukpaetra's Bindings to a Grid:
Fun off-topic anecdote:
One of my very first projects as a self-taught programmer (I was 13), I made a Lights Out game (in VB3 [16 bit]). I'd never heard of code reuse before.
Each cell had a unique, hard-coded function to toggle its (hard-coded) neighbors on click. I think it was an 8x8 grid, so I wrote 64 unique functions.
Yes, it was full of copy-pasta errors where some buttons didn't toggle the right lights.
I wish I still had the source to my Domination game. The buttons had programmatically-generated background images that had to be recalculated every move and selection.
Here's the binary, if anyone wants to decompile it:
@Tsaukpaetra said in The Official Status Thread:
Status: Rediscovered one of my earlier attempts at game programming, a game called Domination! (Not my name for it, that was the actual name of it).
It's an adaptation of a board game:
I think it's pretty fun, but since I hadn't made any kind of AI it does require another player minimum,,,
Screenshot for those who don't want to download random .exe files from strangers:
Edit: oops, wrong post.
-
@error said in Tsaukpaetra's Bindings to a Grid:
Fun off-topic anecdote:
One of my very first projects as a self-taught programmer (I was 13), I made a Lights Out game (in VB3 [16 bit]). I'd never heard of code reuse before.
Each cell had a unique, hard-coded function to toggle its (hard-coded) neighbors on click. I think it was an 8x8 grid, so I wrote 64 unique functions.
Yes, it was full of copy-pasta errors where some buttons didn't toggle the right lights.
Seen that from beginners in my Python class.
One guy wrote an entire hangman game in nested ifs. No loops. All hard-coded for all the possible words he'd decided to include (about 15 or so).
-
@Tsaukpaetra said in Tsaukpaetra's Bindings to a Grid:
Maybe later today I'll anonymize my code and ask what your equivalent might look like for comparison, because I'm just not seeing it...
Not including the Designer-generated code for the
dataGridView1
and themetaMapInfoListItemBindingSource
that came with it, this is my code:Collapsed because tall and I don't trust the Markdown to work properly
///MetaMapData.cs public class MetaMapData { public Dictionary<string, MetaMapInfo> data; public MetaMapData() { data = new Dictionary<string, MetaMapInfo>(); } public MetaMapData(string inString) { data = JsonConvert.DeserializeObject<Dictionary<string, MetaMapInfo>>(inString); } public override string ToString() { return JsonConvert.SerializeObject(data); } public List<MetaMapInfoListItem> ToList() { return data.Select(x => new MetaMapInfoListItem(x.Key, x.Value)).ToList(); } public List<string> Search(string needle) { needle = needle.ToLower(); return data.Where(x => string.IsNullOrEmpty(needle) || x.Key.ToLower().Contains(needle) || x.Value.BW_OBJ_ID.ToLower().Contains(needle) || x.Value.BW_Part_Name.ToLower().Contains(needle) || x.Value.AS_Feature_Point_Name.ToLower().Contains(needle) || x.Value.AS_Feature_Point_ID.ToLower().Contains(needle) || x.Value.AS_Landmark_Name.ToLower().Contains(needle) || x.Value.AS_Landmark_ID.ToLower().Contains(needle) ).Select(x=>x.Key).ToList(); } public List<string> Search(string ID, MetaMapInfo needle) { return data.Where(x => x.Key == ID.PadLeft(3,'0') || !string.IsNullOrWhiteSpace(needle.BW_OBJ_ID) && x.Value.BW_OBJ_ID.ToLower().Contains(needle.BW_OBJ_ID.ToLower()) || !string.IsNullOrWhiteSpace(needle.BW_Part_Name) && x.Value.BW_Body_Part_Name.ToLower().Contains(needle.BW_Part_Name.ToLower()) || !string.IsNullOrWhiteSpace(needle.AS_Feature_Point_Name) && x.Value.AS_Feature_Point_Name.ToLower().Contains(needle.AS_Feature_Point_Name.ToLower()) || !string.IsNullOrWhiteSpace(needle.AS_Feature_Point_ID) && x.Value.AS_Feature_Point_ID.ToLower().Contains(needle.AS_Feature_Point_ID.ToLower()) || !string.IsNullOrWhiteSpace(needle.AS_Landmark_Name) && x.Value.AS_Landmark_Name.ToLower().Contains(needle.AS_Landmark_Name.ToLower()) || !string.IsNullOrWhiteSpace(needle.AS_Landmark_ID) && x.Value.AS_Landmark_ID.ToLower().Contains(needle.AS_Landmark_ID.ToLower()) ).Select(x => x.Key).ToList(); } public class MetaMapInfo { public string BW_OBJ_ID { get; set; } public string BW_Body_Part_Name { get; set; } public string AS_Feature_Point_ID { get; set; } public string AS_Feature_Point_Name { get; set; } public string AS_Landmark_ID { get; set; } public string AS_Landmark_Name { get; set; } } public class MetaMapInfoListItem : MetaMapInfo { public MetaMapInfoListItem(string key, MetaMapInfo value) { ID = key; BW_OBJ_ID = value.BW_OBJ_ID; BW_Part_Name = value.BW_Part_Name; AS_Feature_Point_Name = value.AS_Feature_Point_Name; AS_Feature_Point_ID = value.AS_Feature_Point_ID; AS_Landmark_Name = value.AS_Landmark_Name; AS_Landmark_ID = value.AS_Landmark_ID; } public string ID { get; set; } } } ///MainWindow.cs public partial class MainWindow : Form { APITransport api = new APITransport(); //Yadda class to retrieve and send data // Blah blah InitializeComponent() private async Task RefreshMetaMap() { metaMapData = await api.MetaMapGet(); FilterMetaMap(); } private void MetaMapSearch_Click(object sender, EventArgs e) { FilterMetaMap(); } private void FilterMetaMap() { var searchResults = metaMapData.Search(MetaMapSearchText.Text); metaMapInfoListItemBindingSource.DataSource = metaMapData.ToList().Where(X => searchResults.Contains(X.ID)).ToList(); } private void metaMapInfoListItemBindingSource_AddingNew(object sender, AddingNewEventArgs e) { //Find the next available ID int next = 1; for (; metaMapData.data.ContainsKey(next.ToString("000")); next++) ; e.NewObject = new MetaMapData.MetaMapInfoListItem(next.ToString("000"), new MetaMapData.MetaMapInfo()); } private void dataGridView1_UserAddedRow(object sender, DataGridViewRowEventArgs e) { MetaMapData.MetaMapInfoListItem data = (MetaMapData.MetaMapInfoListItem )e.Row.DataBoundItem; if(data != null) metaMapData.data.Add(data.ID, data as MetaMapData.MetaMapInfo); } private void metaMapInfoListItemBindingSource_CurrentItemChanged(object sender, EventArgs e) { MetaMapData.MetaMapInfoListItem data = (MetaMapData.MetaMapInfoListItem)metaMapInfoListItemBindingSource.Current; metaMapData.data[data.ID] = data as MetaMapData.MetaMapInfo; } private void dataGridView1_RowLeave(object sender, DataGridViewCellEventArgs e) { if(dataGridView1.NewRowIndex == e.RowIndex) { //We left the New Row row without entering any data, delete it from the source. MetaMapData.MetaMapInfoListItem data = (MetaMapData.MetaMapInfoListItem)metaMapInfoListItemBindingSource.Current; metaMapData.data.Remove(data.ID); } } private async void MetaMapSave_Click(object sender, EventArgs e) { MessageBox.Show("MetaMap " + (await api.MetaMapUpdate(replacementMaping: metaMapData) ? " saved." : " failed to save.")); } private void dataGridView1_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e) { MetaMapData.MetaMapInfoListItem data = (MetaMapData.MetaMapInfoListItem)e.Row.DataBoundItem; metaMapData.data.Remove(data.ID); } }
Yeah, it's pretty damn inefficient in many places, but it gets the job done and I don't need anything else besides setting the first column to Read Only so the user doesn't fuck up thee dictionary's key.
I may go back and rename things so it's more obvious, but then, what's the point?
-
@Tsaukpaetra said in Tsaukpaetra's Bindings to a Grid:
thee
-
@pie_flavor said in Tsaukpaetra's Bindings to a Grid:
@Tsaukpaetra said in Tsaukpaetra's Bindings to a Grid:
thee
I blame autocorrect.
-
@error said in Tsaukpaetra's Bindings to a Grid:
Fun off-topic anecdote:
One of my very first projects as a self-taught programmer (I was 13), I made a Lights Out game (in VB3 [16 bit]). I'd never heard of code reuse before.
Each cell had a unique, hard-coded function to toggle its (hard-coded) neighbors on click. I think it was an 8x8 grid, so I wrote 64 unique functions.
Yes, it was full of copy-pasta errors where some buttons didn't toggle the right lights.
A friend of a friend has made a complete simple 3D game with all code inside the main function.
-
@Gąska said in Tsaukpaetra's Bindings to a Grid:
A friend of a friend has made a complete simple 3D game with all code inside the main function.
Did it look like this?
(can't be bothered to check whether it really is all in the main or not...)
#include <time.h> #include <curses.h> #define P(A,B,C,D,E) mvaddch(b+A,a+B,(q[y]&C)?D:E); #define O(A,B,C) case A:if(q[x]&B)C;break; #define R rand() #define U 0,1,4,5 #define J(x) (1<<x) #define V ' ' int r[27] ={0,J(0) ,2,1 ,3,J (2),5,U, U,U,U,U}, u[6]={-1 ,7,49,-49,-7,1},q[343],x,y,d,l=342,a,b ,j='#';int main(){srand(time(0));for(x =0;x<343;x++)q[x]=0;x=R%343;while(l){d =r[R%27];if(((x%7==(x+u[d])%7)+(x/0x31 ==(x+u[d] )/0x31)+ (((x/7)%7 )==(((x+ u[d])/7)% 7))==J(1 ))&&(x+u[d]>=0)&&(x+u[d]<343)){if(!q[x +u[d]]){q[x]+=J(d);x+=u[d];q[x]+=J(5-d );l--;}else if(R<R/0x7){do{x=R%0x157;} while(!q[x]);}}}x=294+R%0x31;initscr() ;noecho( );crmode ();clear (/*|*/); refresh( );while( x>0){move(J(0),60);printw("Level %d",( x/0x31)+J(0)); q[x]|=J(J(3));for(y=(x/ 0x31)*0x31;y<(J(0)+x/0x31)*0x31;y++)if (q[y]&J(J(3))){a=J(0)+(3*((y/7)%7));b= J(0)+(3* (y% 7)); mvaddch( J(1)+((y %7)*3),J (1)+(((y /7)%7)*3 ),V);P(0 ,0,0,0,j )P(3,0,0 ,0,j)P(0,3,0,0,j)P(3,3,0,0,j)P(0,J(0), J(0),V,j)P(0,J(1),J(0),V,j)P(J(0),3,J( 1),V,j)P(J(1),3,J(1),V,j)P(J(1),J(0),4 ,'U',V)P(J(1),J(1),J(3),'D',V)P(J(0),0 ,J(4),V, j)P(J(1) ,0,J(4), V,j)P(3, J(0),J(5 ),V,j)P( 3,J(1),J( 5),V, j)} mvaddch(J (1)+((x%7 )*3),J(1) +(((x/7)% 7)*3),'@') ;refresh() ;switch(getchar()){O('k',J(0), x--)O('j',J(5),x++)O('l',J (1),x+=J(3)-J(0))O('h' ,J(4),x-=7)O('u',4 ,(x+=49, clear()))O('d',8,(x-=49,clear ()))case 'q':x=-1;break;}}clear();refresh (/*v*/); nocrmode ();echo();endwin();if(! x)printf ("You Escaped!\n");exit(0);}
Apparently this is a "3D maze" (3rd winning entry of 1995).
-
@remi said in Tsaukpaetra's Bindings to a Grid:
@Gąska said in Tsaukpaetra's Bindings to a Grid:
A friend of a friend has made a complete simple 3D game with all code inside the main function.
Did it look like this?
No. It was a couple thousand lines long. He only used main because he didn't learn how to use other functions yet. And while still simple, it was much more complex than that.