Problem with SmartDate as a Property!

Problem with SmartDate as a Property!

Old forum URL: forums.lhotka.net/forums/t/2767.aspx


david.wendelken posted on Tuesday, April 24, 2007

Here's a sample test class that we'll use to illustrate this problem:

public class TestClass
{
     private SmartDate _mySmartDate = new SmartDate();

     public SmartDate MySmartDate
    {
          get { return _mySmartDate; }
          set { _mySmartDate = value; }
     }

}

Here's a test script that illustrates the problem:


// This works great!
SmartDate d = new SmartDate();
d.Date =
DateTime.Now;

// This does't work at all.
TestClass
tc = new TestClass();
tc.MySmartDate.Date = DateTime.Now;

// compile error for above line is this:
//Cannot modify the return value of 'TestClass.MySmartDate' because it is not a variable 

// This does work.
TestClass tc = new TestClass();
tc.MySmartDate = n
ew SmartDate(DateTime.Now);

It appears that the set logic in the property definition trumps your ability to modify properties of the property object.

Is this intended?  Is there a work-around? 

jhw replied on Tuesday, April 24, 2007

This has always been a problem with smartdate. Personally I use them internally, but expose them a a date type and do the converson in the property. Till something better comes along. I hope you find an answer

david.wendelken replied on Tuesday, April 24, 2007

If that type of trick has to be done, wouldn't it be better to expose it as a string?

That way, an empty string would represent null.  Otherwise, the DateTime class will expose null dates as some day in the 1700's, won't it?

 

jhw replied on Tuesday, April 24, 2007

Yes that is a good point. I have never tried exposing them as a string but maybe that would be better. Does anyone else have an opinion on this.

Yang replied on Tuesday, April 24, 2007

There is also a Nullable(of T) generics class that you can use like

Private mNullableDate As Nullable(Of Date) = New Nullable(Of Date)

This allows you to represent the date as null. You need to subclass the  DateTimePicker to allow binding to this type of object.

 

RockfordLhotka replied on Tuesday, April 24, 2007

Honestly the whole point of SmartDate is for internal use so you can expose the value as a string. SmartDate understands the idea of an "empty" date, and that an empty string equates to an empty date.

If you don't need the concept of a date being empty, then you really don't need SmartDate in most cases. If a date is required, you can just use DateTime. If it can be null you can use Nullable<DateTime>.

But if you need the concept of "empty" (which is quite different from null), then SmartDate is your friend.

Also, if you want to expose a date value in a simple textbox so the user can type dates free-form, use parsing shortcuts like "Monday" or "Today" or "t" then again, SmartDate is your friend, because it handles all the string-to-and-from-date translation.

david.wendelken replied on Wednesday, May 09, 2007

RockfordLhotka:

Honestly the whole point of SmartDate is for internal use so you can expose the value as a string. SmartDate understands the idea of an "empty" date, and that an empty string equates to an empty date.

If you don't need the concept of a date being empty, then you really don't need SmartDate in most cases. If a date is required, you can just use DateTime. If it can be null you can use Nullable<DateTime>.

But if you need the concept of "empty" (which is quite different from null), then SmartDate is your friend.

Also, if you want to expose a date value in a simple textbox so the user can type dates free-form, use parsing shortcuts like "Monday" or "Today" or "t" then again, SmartDate is your friend, because it handles all the string-to-and-from-date translation.

Ok, you sold me on the approach of exposing the property as a string.

However, once we bind the collection to a datagrid, we want to allow the user to sort it.

And, of course, the datagrid sorts our dates and numbers as strings.

Is there an easy workaround for this?

Brian Criswell replied on Wednesday, May 09, 2007

Wrap your list in an ObjectListView.  It provides both interface and event options for changing how a property is sorted.

JoeFallon1 replied on Thursday, May 10, 2007

Brian's solution is a good one.

A quick and dirty one is to have two properties for the date - one as a String and the other as a Date.

Bind the grid to the string property but sort by the date property.

Joe

 

david.wendelken replied on Thursday, May 10, 2007

Brian,

I had seen your ObjectListView before, but hadn't gotten around to trying it.

Very nice!

Pretty intuitive, and it seems to me much easier to use than the SortedBindingList and FilteredBindingList.

Thanks for the good work, and for the tip!

========================================

FYI, the Sort and Filter help text need some examples of usage, such as:

Sort:

"PropertyOne asc, PropertyFive desc"

Filter:

"PropertyOne >= 10 and PropertyFive like 'M%'"

 

 

david.wendelken replied on Thursday, May 10, 2007

We're looking at implementing the IExtendSort in our RuleBusinessBase class, which is our development subclass of BusinessBase.

We use the standard naming convention of "PropertyName" for a property and "_propertyName" for the corresponding private variable.  We're hoping that we can deduce the private variable name and use reflection to get the value to return. 

Advantages (we hope!):

Disadvantages (we hope not!):

Have you tried this?  If not, we'll post our progress.

Brian Criswell replied on Thursday, May 10, 2007

Interesting solution.  It should work fine.  I would recommend caching the property descriptors and field descriptors per object type.

The ObjectView (the item in an ObjectListView) uses reflection already, and the sorting code uses reflection to get at the property values.  I once ran a real world test, and if memory serves, it sorted 8000 items in 2 or 3 seconds.

Filtering is quite a bit slower though because each item is passed into a DataView to see if it passes the filter criteria.  This allows for the easy filter syntax, but I think that one of these days I need to use one of the free lexers/parsers to parse the filter string and test the values directly.

david.wendelken replied on Thursday, May 10, 2007

Brian Criswell:
Interesting solution.  It should work fine.  I would recommend caching the property descriptors and field descriptors per object type.

Good idea!  My colleague, who's taking it to the next step, just left for the day, but he felt pretty confident he would get it working in the morning.  I'll pass on your suggestion. 

I'm really excited for it to work, because that would become one less thing I would have to worry about going wrong, and therefore one less thing to test. :)

david.wendelken replied on Tuesday, May 15, 2007

We moved the code up to our version of BusinessBase and it worked great.

So, the only thing we have to do in our business objects is to claim they implement the interface.

JoeFallon1 replied on Wednesday, May 16, 2007

Would you mind sharing some code so we can see how it was done?
Thanks.

 

david.wendelken replied on Wednesday, May 16, 2007

JoeFallon1:

Would you mind sharing some code so we can see how it was done?
Thanks.

Not at all!  

But the current version is company-specific and company-proprietary. :(

I have to redevelop it at home.  I'm testing a new DotNetNuke module version this week, so it probably won't happen until next week.

david.wendelken replied on Wednesday, May 16, 2007

Ok, I got bored at lunch. :)

This would go into your subclass of BusinessBase.

/// <summary>
/// All internal variables that are SmartFields (SmartDate, SmartInt16, etc.), if exposed as properties,
/// are exposed as strings. This makes binding extremely easy, but it also means that the default sorting
/// behavior is to sort those properties as strings. This routine works in conjunction with the sorting
/// capability provided by <see cref="ObjectListView"/> so that the objects are sorted
/// by their underlying datatypes, not their public string values.
///
/// Note:
///
/// If the property is not a string, it is assumed that the property itself should be used to sort by.
///
/// This implementation is based upon correctly following a simple naming convention. If a property is named
/// PropertyNameHere, it's underlying private variable will be named _propertyNameHere. If such a variable
/// is found we return an object of the appropriate private variable type. Otherwise, we
/// just return the value we were given.
/// </summary>
/// <param name="property">Contains a PropertyDescriptor for the property being sorted.</param>
/// <param name="value">Contains an object of the appropriate property type, containing that property's value.</param>
/// <returns>An object of the type we want to sort by, containing the value to sort by.</returns>

public object GetSortValue(PropertyDescriptor property, object value)
{
    // We are only interested in doing special handling when the public variable is a string.
    // Otherwise, leave well enough alone and return
the value we were supplied.
    //

    // So, if the property is not a string, we can short-circuit the test and return the value we
    // were given.
    if (property.PropertyType.FullName != "System.String")
   {
        return value;
   }

   // Get a list of all fields in the object.
   FieldInfo[] fi = this.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

   // We assume the property name starts with an uppercase letter, and that the corresponding private variable
   // starts with an underscore and a lowercase version of the starting letter.
   int len = property.Name.Length - 1;

   // privateVariableName = the deduced corresponding private variable name.
   string privateVariableName = "_"
                                                       + (property.Name.ToCharArray()[0].ToString().ToLower())
                                                       +  property.Name.Substring(1, len);

    // Check each variable name for a match.
    foreach (FieldInfo f in fi)
    {
        // We have the correct private variable if the names match.
        if (f.Name.Equals(privateVariableName))
       {
           
// Return the underlying variable value.
            return f.GetValue(this);
       }

    }

    return value;
}

}

JoeFallon1 replied on Thursday, May 17, 2007

Thanks for sharing the code.

I see what you mean now.

Thanks.

Joe

Here is a VB version which assumes private variables are named: mPropertyName.

Public Function GetSortValue(ByVal [property] As System.ComponentModel.PropertyDescriptor, ByVal value As Object) As Object
 
If [property].PropertyType.FullName <> "System.String" Then
   
Return value
 
End If

  'Get a list of all fields in the object.
 
Dim fi As FieldInfo() = Me.GetType().GetFields((BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Instance))

  'We assume that the private variable corresponding to the Property name starts with an m and uses the same PropertyName.
 
Dim privateVariableName As String = "m" & [property].Name

  Dim f As FieldInfo

  For Each f In fi
   
If f.Name.Equals(privateVariableName) Then
     
Return f.GetValue(Me)
   
End If
 
Next f

 
Return value
End Function

This routine is added to your Base class that Inherits BusinessBase and it works in conjunction with the sorting  capability provided by <see cref="ObjectListView"/> so that the objects are sorted by their underlying datatypes, not their public string values.

A BO that wants to use this feature should simply:

Implement IExtendSort

The ObjectListView class will then call this code automatically if it needs to sort properties on that BO.

 

Wbmstrmjb replied on Friday, April 24, 2009

Rocky,

Can you please explain what you mean by a difference between a null date and an empty date? This is easy to understand for a string, but I do not see the difference for a date since an empty date is not a date where as a zero length string is a set value just with no characters.

What does an empty date buy you that is different from a null date? Is the string implementation a hold over from .NET 1.1 and is not necessary with nullable types?

RockfordLhotka replied on Friday, April 24, 2009

I've explained this several times on the forum in the past.

There are rules for null values. Anything compared to null is null.

But in many applications an empty date isn't null. In fact, an empty date is
either infinitely far in the past or the future. You can compare an empty
date to a specific date and find out if it is greater than or less than.

This is particularly apparent in systems that allow the user to type in a
date value on a TextBox - like Quicken, Money and every point of sale or
sales order system I've ever created over the past couple decades. Users
hate masked edit boxes and calendar controls - at least in any heads-down
app where productivity is valuable.

And in those cases, what does it mean when the Ship Date field is empty? It
means the order hasn't shipped yet - so the date is effectively infinitely
far in the future. Reports or other comparisons should consider that empty
value as bigger than any other value.

That isn't null - because if it were null you couldn't get a meaningful
comparison result.

So empty is not a specific date, but it isn't a null date either.

Rocky

Wbmstrmjb replied on Monday, April 27, 2009

Thanks Rocky. I hope the new FAQ will kill our repetitive questions that I'm sure you hate.

Copyright (c) Marimer LLC