Tsaukpaetra's Bindings to a Grid



  • @TwelveBaud said in The Official Status Thread:

    @Tsaukpaetra Why do you have a List that's decoupled from your Dictionary? Why not use a BindingList so you can more easily track changes? Why not use a custom IDataAdapter to create an appropriate DataSet 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 that Dictionary 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 or BindingList to hold the objects, then use Linq's ToLookup() to get a sort-of-but-not-really dictionary. Unlike ToDictionary(), 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 what DataGridViews are actually designed to work with. DataSets 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 of IDataAdapter it's useful for other types of backing stores as well. Implement Fill() to convert your dictionary to a DataSet, and implement Update() 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 a List. Adding an event handler for ListChanged 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.


  • Notification Spam Recipient

    @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.


  • Notification Spam Recipient

    @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.


  • Trolleybus Mechanic

    @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:

    1. Get the dictionary and transform it into a list
    2. Shove the list into the dataSourceThingamabob
    3. Let the user fuck with it
    4. Grab the list and convert it back into the dictionary
    5. 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.


  • Notification Spam Recipient

    @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:

    1. Get the dictionary and transform it into a list
    2. Shove the list into the dataSourceThingamabob
    3. Let the user fuck with it
    4. Grab the list and convert it back into the dictionary
    5. 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.


  • Trolleybus Mechanic

    @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.


  • Notification Spam Recipient

    @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.


  • Trolleybus Mechanic

    @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!)


  • Notification Spam Recipient

    @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?


  • Trolleybus Mechanic

    @Tsaukpaetra Because it's your code.


  • Notification Spam Recipient

    @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.


  • Trolleybus Mechanic

    @Tsaukpaetra I have nothing to say to that...


  • Considered Harmful

    @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.


  • Notification Spam Recipient

    @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...


  • Trolleybus Mechanic

    @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*)


  • Considered Harmful

    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.


  • Notification Spam Recipient

    @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).


  • Trolleybus Mechanic

    @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.


  • Considered Harmful

    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.


  • Notification Spam Recipient

    @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:

    How To Play The Game Domination The Game of Strategy and Supremacy #4207, 1982 Milton Bradley – 08:36
    — Lucky Penny Shop

    Domination_Game.exe

    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:

    0_1481347353254_upload-cdcc2ed1-e898-44a5-bcca-3e46ef5bdf6f

    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).


  • Notification Spam Recipient

    @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 the metaMapInfoListItemBindingSource 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?


  • Considered Harmful


  • Notification Spam Recipient


  • Banned

    @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).


  • Banned

    @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.


Log in to reply