Friday, April 29, 2011

3 steps to enable automatic sorting of business object using BindingList

1. Create these 2 classes, just COPY AND PASTE!
You need to have "using System.ComponentModel" wherever you put these


    internal class SortComparer<T> : IComparer<T>
    {
        private PropertyDescriptor m_PropDesc = null;
        private ListSortDirection m_Direction = ListSortDirection.Ascending;

        public SortComparer(PropertyDescriptor propDesc, ListSortDirection direction)
        {
            m_PropDesc = propDesc;
            m_Direction = direction;
        }

        int IComparer<T>.Compare(T x, T y)
        {
            object xValue = m_PropDesc.GetValue(x);
            object yValue = m_PropDesc.GetValue(y);
            return CompareValues(xValue, yValue, m_Direction);
        }

        private int CompareValues(object xValue, object yValue, ListSortDirection direction)
        {
            int retValue = 0;
            if (xValue is IComparable) //can ask the x value 
            {
                retValue = ((IComparable)xValue).CompareTo(yValue);
            }
            else if (yValue is IComparable) //can ask the y value 
            {
                retValue = ((IComparable)yValue).CompareTo(xValue);
            }
            //not comparable, compare string representations 
            else if (!xValue.Equals(yValue))
            {
                retValue = xValue.ToString().CompareTo(yValue.ToString());
            }
            if (direction == ListSortDirection.Ascending)
                return retValue;
            else
                return retValue * -1;
        }
    }
    public class SortableBindingList<t> : BindingList<t>
    {
        private bool m_Sorted = false;
        private ListSortDirection m_SortDirection = ListSortDirection.Ascending;
        private PropertyDescriptor m_SortProperty = null;

        protected override bool SupportsSortingCore
        {
            get
            {
                return true;
            }
        }

        protected override bool IsSortedCore
        {
            get
            {
                return m_Sorted;
            }
        }

        protected override ListSortDirection SortDirectionCore
        {
            get
            {
                return m_SortDirection;
            }
        }

        protected override PropertyDescriptor SortPropertyCore
        {
            get
            {
                return m_SortProperty;
            }
        }

        protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            m_SortDirection = direction;
            m_SortProperty = prop;
            m_Sorted = true;
            var listRef = this.Items as List<t>;
            if (listRef == null)
                return;
            var comparer = new SortComparer<t>(prop, direction);

            listRef.Sort(comparer);

            OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }
    } 


2. In your business object that was formerly inheriting BindingList, it now needs to inherit SortableBindingList, like so:
public class BusinessObjList : SortableBindingList<BusinessObj>

3. In your DataGridView's Column Collection, set all the columns you want to be sortable to SortMode=Automatic


Reference
1. http://xiaonanstechblog.blogspot.com/2009/03/how-to-enable-column-sorting-on.html

Friday, April 22, 2011

FileSystemWatcher OnChange event fires twice

THIS IS A KNOWN BUG.

So here's what I did to deal with this:


I am only monitoring 1 file (a config file for a service, so it can update without having be restarted). The way I solved the 2 onchanged event firing problem is like this:

internal class ConfigFileWatcher
{
    private FileSystemWatcher fileSysWatcher;
    private bool eventFired;
    internal ConfigFileWatcher()
   {
      eventFired = false;
     //initialize fileSysWatcher with path and filter, and add OnChange event handler
   }
        private void fileSysWatcher_Changed(object sender, System.IO.FileSystemEventArgs e)
        {
            if (eventFired)
            {
                eventFired = false;
                return;
            }
            else
                eventFired = true;
           
            //do stuff with the file
}
}

Since we know for sure that the OnChange event is going to fire twice, it makes sense to use a bool like this.

I know this isn't what your problem is exactly, but maybe you could experiment by using a Dictionary with FullPath = key, EventFired Bool = value

So for example, Dictionary<string, bool> eventFireDict

In the OnChanged event check for that full path

//pseudo-code
if(Dictionary Contains Full Path)
{
  if (Key's Value = True)
   {
    set key's value to False
    return
   }
   else
  {
     set key's value to True
   }
}
else
Insert <Full Path, True> into Dictionary
}

Notice, you don't even need a Dictioanry<string, bool> if you're only looking for a change in that file once. If you're just looking for a change once, I'm pretty sure you could just use a List<string> like this

//pseudo-code

if(List<string> contains Full Path)
{
//event already fired for this full path, so remove the full path and return so it doesn't process that file //twice
List.remove(Full Path);
return;
}
else
{
//event hasn't fired yet for this Full Path, so put it in the List and then process the Full Path
List.add(Full Path);
process(Full Path);
}


References
http://weblogs.asp.net/ashben/archive/2003/10/14/31773.aspx

Tuesday, April 12, 2011

How to retrieve data from modal view

I have CardFormViewController and PickerViewController. CardFormViewController needs to ask the user for credit card type, such as "Visa" and "MasterCard". In order to ask the user I want to use a UIPickerView. However, the UIPickerView is TOO big to stick on a form with other elements.

The solution I use is to stick the UIPickerView onto it's own View (hence PickerViewController) and open it up modally via
    pickerView = [[PickerViewController alloc] autorelease];
    pickerView.mode = CardType;
    pickerView.view.backgroundColor = [UIColor clearColor];
    pickerView.modalTransitionStyle = UIModalTransitionStylePartialCurl;
    [self presentModalViewController:pickerView animated:YES];
I'm sure you have already done this considering you googled how to retrieve the data from it. So here's how to retrieve the data:

1. In CardFormViewController create a property, such as a UILabel. (don't forget to synthesize, release, link in IB :))

2. Make sure you have PickerViewController set as a UIPickerViewDelegate like @interface PickerViewController : UIViewController <UIPickerViewDelegate>. Also make sure you have implemented the required protocol (see UIPickerView.h or documentation on Apple's site for that if you need help)

3. Implement the UIPickerViewDelegate method - (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component





{
//IF YOU ARENT using NavigationController as your Root Controller, uncomment this code and comment out the other code
/*
    CardFormViewController *vc = (CardFormViewController*)self.parentViewController;
    vc.lblTest.text = [cardTypeArray objectAtIndex:row];



*/
//IF YOU ARE using NavigationController as your Root Controller, use code below
    UINavigationController *nc = (UINavigationController*)self.parentViewController;
   
   CardFormViewController *vc = (CardFormViewController*)[nc.viewControllers objectAtIndex:(nc.viewControllers.count - 1)];
    vc.lblTest.text = [cardTypeArray objectAtIndex:row];

}
There was an error in this gadget