0

I have a few ListViews that depend on the contents of Dictionary<string,string>s. I also have one function per dictionary for updating its associated ListView. However, these have to be called manually and this can cause all kinds of problems if forgotten. Is it possible to call a function whenever any value within a dictionary is changed?

My initial thoughts were to use DataBinding but Listview doesn't support this in WinForms or to raise some custom events but I wouldn't even know where to begin with that.

Is there a simple way to acheive this?

Just for clarification I'm talking about something that has this effect:

Dictionary[key] = "this";  //Automatically runs UpdateDictionaryBox();
Dictionary.Add(key,value); //The same
Dictionary.Remove(key);    //And again
ScottishTapWater
  • 3,656
  • 4
  • 38
  • 81
  • 2
    Something like a [`ObservableDictionary`](http://blogs.microsoft.co.il/shimmy/2010/12/26/observabledictionarylttkey-tvaluegt-c/) ? – Tim Schmelter Nov 25 '16 at 13:32
  • Why not using `DataGridView` bound to a `BindingList` where T implemented `INotifyPropertyChanged`? – Reza Aghaei Nov 25 '16 at 13:47
  • 1
    Normally you can avoid such things by encapsulation and single responsibility classes. If you had a class that holds this dictionary and this would be private, no one could access it but this class. Then you could provide properties/methods to change, add or remove items. This is the only place where you need to handle this, f.e. by triggering a custom event that can be handled, there you could call `UpdateDictionaryBox` – Tim Schmelter Nov 25 '16 at 13:48
  • @TimSchmelter so essentially just create a shell class which keeps track of the dictionary and the control, then get and set through that? – ScottishTapWater Nov 25 '16 at 13:54
  • @JamesHughes: Yes, a class which only responsibility is to store and maintain this dictionary (or related stuff). This can be initialized in the controller class. This "dictionary-class" doesn't know your `UpdateDictionaryBox`, actually it should not know it because it's not it's job. So you need a way to notify the controller, the normal way is to use a custom event. Then it gets notified and you call the method in the event-handler. It's not really simple but clean, safe and resusable. – Tim Schmelter Nov 25 '16 at 13:59
  • That makes a lot of sense, thank you. If you would write that up as an answer I will happily accept it. Unless you're happy for me to do that for you. – ScottishTapWater Nov 25 '16 at 14:01
  • 2
    Also you may want to take a look at this post: [How to display a dictionary using DataGridView?](https://stackoverflow.com/questions/40566268/how-to-display-a-dictionary-using-datagridview) – Reza Aghaei Nov 25 '16 at 14:09
  • Thanks @RezaAghaei, unfortunately my UI design has been slightly dictated to me. A `DataGridView` isn't what they want, but that post is useful for future reference. – ScottishTapWater Nov 25 '16 at 14:13

1 Answers1

0

All credit to Tim Schmelter as his comment led me to this solution!

Essentially, the easiest method is to create a wrapper class around the Dictionary<string,string> and the ListView then use this to implement all of the dictionary methods that are needed and call the update at the end of .Add, .Remove [key] = value etc.

This way, once the class is implemented correctly, it emulates data binding.

My resulting class is:

class DictionaryListViewPseudoBinder : IEnumerable
{
    private ListView ListView { get; }
    private Dictionary<string,string> Dictionary { get; set; }

    public DictionaryListViewPseudoBinder(ListView listView)
    {
        ListView = listView;
        Dictionary = new Dictionary<string, string>();
    }

    public string this[string key]
    {
        get
        {
            return Dictionary.ContainsKey(key) ? Dictionary[key] : "";
        }
        set
        {
            if (Dictionary.ContainsKey(key))
            {
                Dictionary[key] = value;
                RepopulateListView();
            }
            else
            {
                MessageBox.Show("Dictionary does not contain key " + key + " aborting...");
            }
        }
    }
    public void Add(string key, string value)
    {
        if (!Dictionary.ContainsKey(key))
        {
            Dictionary.Add(key, value);
            RepopulateListView();
        }
        else
        {
            MessageBox.Show(string.Format("The Entry \"{0}\" already exists in {1}",key,ListView.Name));
        }
    }

    public void Remove(string key)
    {
        if (Dictionary.ContainsKey(key))
        {
            Dictionary.Remove(key);
        }
    }

    public bool ContainsKey(string key)
    {
        return Dictionary.ContainsKey(key);
    }

    public bool ContainsKVP(KeyValuePair<string, string> kvp)
    {
        if (!Dictionary.ContainsKey(kvp.Key))
        {
            return false;
        }
        else
        {
            return Dictionary[kvp.Key] == kvp.Value;
        }
    }
    private void RepopulateListView()
    {
        ListView.Items.Clear();
        foreach (KeyValuePair<string, string> kvp in Dictionary)
        {
            ListView.Items.Add(kvp.Key).SubItems.Add(kvp.Value);
        }
    }

    public IEnumerator GetEnumerator()
    {
        return Dictionary.GetEnumerator();
    }
}

NB: The above is not fully tested yet and not all methods are fully implemented at this time, but it shows the general framework necessary for this functionality.

ScottishTapWater
  • 3,656
  • 4
  • 38
  • 81
  • Don't use the contains check with a dictionary, it causes two lookups one for the contains and one for the operation inside the if. Use the try methods (TryAdd , etc.) Instead. – Scott Chamberlain Nov 25 '16 at 17:16