Search code examples
c#datagridviewinotifypropertychangedbindinglistsystem.componentmodel

How to use a static utility method for property setters in a utility class


I'm trying to achieve two-way binding between a DataGridView and a BindingList that provides data for the DGV. Some columns do not yet reflect changes in the underlying list and I think it's because I have not provided property setter(s) to notify of property changes. Rather than code the setter for the Rows property the same way I did for the Process property, I'm trying to get more "elegant" and I realize I am stuck....

I stumbled upon a very interesting writeup for a more elegant approach and I'm trying to implement the concepts of it (please see): http://www.gavaghan.org/blog/2007/07/17/use-inotifypropertychanged-with-bindinglist/

Here is the code from Mike's article I want to use (established as Utilities.cs in my CBMI.Common project):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace CBMI.Common
{
public static class Utilities
{
    public static bool Set<T>(object owner, string propName,
        ref T oldValue, T newValue, PropertyChangedEventHandler eventHandler)
    {
        // make sure the property name really exists
        if (owner.GetType().GetProperty(propName) == null)
        {
            throw new ArgumentException("No property named '" + propName + "' on " + owner.GetType().FullName);
        }
        if (!Equals(oldValue, newValue))  // we only raise an event if the value has changed
        {
            oldValue = newValue;
            if (eventHandler != null)
            {
                eventHandler(owner, new PropertyChangedEventArgs(propName));
            }
        }
    return true;    // Please NOTE: I had to add this statement to avoid compile error:
        // "not all code paths return a value".
    }
}
}

So, my FIRST QUESTION about this: The author did not have a return statement in his article and I added it which resolved the compiler error. I'm guessing the eventHandler executes and returns and that this was an author omission and this should return true as the method wants a bool return type. Is that correct assumption?

My 2nd QUESTION shows what a C# rookie I am when I try to use this helper method above. I have coded this class into a separate file called InputFileInfo.cs in the same project (and namespace) as the above:

    using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace CBMI.Common
{
    public class InputFileInfo : INotifyPropertyChanged
    {
    private bool processThisFile;
    public bool Process
    {
        get { return processThisFile; }
        set
        {
        processThisFile = value;
        this.NotifyPropertyChanged("Process");
        }
    }

    public string FileName { get; set; }

    private long rowsReturned;
    public long Rows
    {
        get { return rowsReturned; }
        set
        {
        Utilities.Set(this, "Rows", ref rowsReturned, value, PropertyChanged);
        }

    }
    public string Message { get; set; } 

    // constructor
    public InputFileInfo(string fName)
    {
        Process = true;
        FileName = fName;
        Rows = 0;
        Message = String.Empty;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
    }
}

The setter for the 2nd property in this class is where I try to use Mike's static method:

Utilities.Set(this, "Rows", ref rowsReturned, value, PropertyChanged);

If I remove Utilities.Set and just code it as follows:

Set(this, "Rows", ref rowsReturned, value, PropertyChanged);

..then I get the compiler complaining that "the name 'Set' does not exist in the current context".

I tried adding a using Utilities; directive but that did not fix the problem.

Finally, I do not understand the parameters: ref T oldValue, T newValue
nor the parameter called value where the Set method is invoked.

Can someone please help me over these multiple confusions about this code so I can use these more advanced ideas?

---- EDIT UPDATE ---- Two good answers helped me get this working. The "2nd question" in the original post above remains a bit elusive. Added comments for each requesting a "best practice" on how to package this so I can use the simple invoking syntax as in Mike's original article. That is, I'm seeking to invoke "helper" static methods by the method name only. I want to understand how to invoke like:

set
{
    Set(this, "Rows", ref rowsReturned, value, PropertyChanged);
}

instead of having to code as:

set
{
    Utilities.Set(this, "Rows", ref rowsReturned, value, PropertyChanged);
}

I got this working by coding Utilities.Set but I guess the question morphs a bit into - "Where do I put static methods and how to call them so I don't have to "qualify" them with the classname?" I would like to understand how to package generally useful "utility" type methods that don't require an instance of an object. In this case, the static method is called Set but I'd like to be able to add other static methods such as:

public static int HelpfulMethodXXXX(string s, int num)

I have a separately compiled DLL (Vstudio project) containing only class file(s). Ultimately, I'd like to think I could use this class in other applications.

Where is the best place to declare these sort of static methods such that they could be invoked as:

int i = HelpfulMethodXXXX("Sample", testNumber);

instead of:

int i = ContainingClassName.HelpfulMethodXXXX("Sample", testNumber);

Solution

  • 1: All non-void methods need to have explicit return statements.

    2: CMBI.Common is the namespace. Utilities is the name of your class. Set() is a function of your class.

    The call to Set() only makes sense in the context of the Utilities class. Set() is not part of the global namespace - as such, if you want to call Set() outside of the Utilities class, you have to specify that you want the Utilities.Set(), as opposed to SomethingElse.Set(). (Inside Utilities, the compiler understands that Set() refers to Utilities.Set()).

    Using statements can only include namespaces (CMBI.Common), or specific classes inside namespaces, if you don't want every class in the namespace (CMBI.Common.Utilities). They cannot, however, turn a class function into a global function.

    3: T refers to the name of the Generic type that this function operates on. http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx

    Generics allow the same code to manipulate, say, a collection of integers, and a collection of strings, while enforcing compile-time type safety (The compiler will give an error, if you try to push an integer into a collection of strings.)

    ref means that the parameter is passed as a reference - and that changes made to the parameter within the body of a function will propagate to the parameter's value in the context of the function's caller.