Blogs

Paul Kimmel's Blog

July 2009 - Posts

  • Google Text Messaging

         

    My girlfriend Shelly is by no means a technophile. She loves text messaging but doesn’t care how it works. If her PC stops working then she just walks over to another one or uses a friend’s. Her PC can sit there for months as long as she can get her email and text messages some where she is happy. Of course, this doesn’t bode well for Microsoft because Microsoft is historically a company dependent on technophiles—people like me and probably you—that are willing to guts balls it and patch software, tweak the registry, and even replace parts if necessary. Unfortunately users in the 00s—“naughties”—seem to care less and less about operating systems, suites of software products, and the like. Newer users just want to chat, text, email (less and less) and be connected to information without all of the techno-babble hassle that got this PC age kick started in the first place. And, on that vein of thought my non-technophile girlfriend introduced me to this bit of text messaging wizardry.

    (I may be the last to know about sending text messages to Google, but just in case I am not here goes.)

    If you send a text message to Google (466453) with the name of something like ‘Microsoft’ then Google will send you a text message back. I texted ‘Microsoft’ to Google and got a stock quote back. I texted ‘Michigan Athletic Club’ to Google (466453) and got the complete address and phone number of the Michigan Athletic Club. Don’t quote me but she said the texts are free. Maybe they are free, maybe not, but I will bet getting a phone number this way is cheaper than dialing 411 (for information in the U.S.). After initial queries Google started sending me useful hints. Apparently you can send commands to Google like SET LOCATION and CLEAR LOCATION to indicate your physical locale on the planet. You can send commands like MAP, HELP MAP, HELP TRANSLATION, VIEW LOCATION, and I suspect many more. When I sent MAP followed by my street address Google sent the complete mailing address and phone number.

    As a final test—I do have real work to do—I sent the very generic text message ‘HELP’ and Google sent me messages for weather shortcuts {e.g. ‘W NYC’}, a message for finding local pizza places and even the the command to abort sending, STOP, and another for random tips ‘TIPS’. I typed TIPS  and got information about using Google text as a calculator, finding movies, flight information, sports venues, and even the price of an iPod.

    Happy texting. Also, check with your service provider before you get crazy; I’d hate for you to have a huge bill from texting Google.

  • Document MS Access Table Schemas

         

    There are a lot of little things one has to do to complete any project. Exporting files, tweaking data, documenting things, coming up with graphics are just some of the skills developers need. Technical books projects are a lot like software projects. There are a little of extra little computer skills, sometimes basic but useful, that have to be managed to get a finished product. One of the samples in the book project—Professional DevExpress ASP.NET Controls—uses an Access database. It was useful to describe the schema of the Access for the Access tables because they were to mapped to another implicit schema. The odd challenge was that there didn’t seem to be an analog for querying the schema of Access tables in Access. You can show hidden objects and query tables like MSysAccessObjects, which will give you some information, but it doesn’t appear that you can query schema tables like you can in SQL Server. (Perhaps one of you knows how to do this and can enlighten me.)

    You can of course write code to load a table and dump table and field names, data types, and sizes, and you can use the tools in MS Access to create schema reports. The following numbered steps describe how to create a report for the Cars and CarScheduling tables in Developer Express’ CarsDB.mdb database. You can of course substitute any Access database and tables, if you’d like. To print database documentation follow these steps:

    1. Open MS Access and open your database. (For this sample the CarsDB.MDB sample database installed with ASPxperience was used)
    2. Press ALT+T+Y+D to open the Documentor. (This is a legacy menu option that still works as long as you know the shortcut)
    3. Click the Table tabs
    4. In the Documentor Table tab check the Cars and CarScheduling tables (see Figure 1)
    5. Click the Options button
    6. In the Include for Table group check Properties only
    7. In the Include for Fields group select the Names, Data Types, and Sizes radio button
    8. In the Include for Indexes group select the Nothing radio button (see Figure 2).
    9. Click OK to close the Print Table Definition dialog
    10. Click OK to generate the documentation
    11. When the documentation report is finished the Print Preview tab will be displayed. Click Text File in the Data section to export the report to a text file
    12. In the Export – Text File dialog (see Figure 3)
    13. Check open the destination file after the export operation is complete
    14. Click OK
    15. For the encoding choose Windows default

    The export documentation is provided in Listing 1. That’s all there is to it.

    image
    Figure 1: The Documentor in MS Access.

    image
    Figure 2: The Print Table Definitions dialog lets you choose what you want to print. 

    image
    Figure 3: Export the printed result to a text file and open the results.

    Listing 1: The exported text content based on the options selected.

    C:\Websites\SchedulerDemo\App_Data\CarsDB.mdb                                          Friday, July 03, 2009

    Table: Cars                                                                                          Page: 1

    Properties

    DateCreated:              7/16/2002 5:48:37 PM        DefaultView:              2

    GUID:                     {guid {8EECDB13-            LastUpdated:              7/4/2006 12:02:46 PM

                              A77D-45D5-84D5-

    NameMap:                  Long binary data            OrderByOn:                False

    Orientation:              Left-to-Right               RecordCount:              15

    Updatable:                True

    Columns

             Name                                                  Type                        Size

             ID                                                    Long Integer                             4

             Trademark                                             Text                                    50

             Model                                                 Text                                    50

             HP                                                    Integer                                  2

             Liter                                                 Double                                   8

             Cyl                                                   Byte                                     1

             TransmissSpeedCount                                   Byte                                     1

             TransmissAutomatic                                    Text                                     3

             MPG_City                                              Byte                                     1

             MPG_Highway                                           Byte                                     1

             Category                                              Text                                     7

             Description                                           Memo                                     -

             Hyperlink                                             Text                                    50

             Picture                                               OLE Object                               -

             Price                                                 Currency                                 8

             RtfContent                                            Memo                                     -

    C:\Websites\SchedulerDemo\App_Data\CarsDB.mdb                                          Friday, July 03, 2009

    Table: CarScheduling                                                                                 Page: 2

    Properties

    DateCreated:              7/15/2005 5:09:08 PM        DefaultView:              2

    GUID:                     {guid {26F80294-9FDD-       LastUpdated:              11/1/2007 1:31:56 PM

                              4A29-8A9C-

    NameMap:                  Long binary data            OrderByOn:                True

    Orientation:              Left-to-Right               RecordCount:              20

    RowHeight:                1425                        Updatable:                True

    Columns

             Name                                                  Type                        Size

             ID                                                    Long Integer                             4

             CarId                                                 Long Integer                             4

             UserId                                                Long Integer                             4

             Status                                                Long Integer                             4

             Subject                                               Text                                    50

             Description                                           Memo                                     -

             Label                                                 Long Integer                             4

             StartTime                                             Date/Time                                8

             EndTime                                               Date/Time                                8

             Location                                              Text                                    50

             AllDay                                                Yes/No                                   1

             EventType                                             Long Integer                             4

             RecurrenceInfo                                        Memo                                     -

             ReminderInfo                                          Memo                                     -

             Price                                                 Currency                                 8

             ContactInfo                                           Memo                                     -

  • Using Custom Objects with ASPxScheduler

         

    I was curious and surprised to discover that some employers are literally listing “Developer Express controls experience” as a pre-requisite for some programming employment. For some employers it is not enough to be a good developer, a good ASP.NET programmer, a good database guy, and a good JavaScript guy (or gal). Some employers want to know that a potential candidate has real experience with Developer Express controls. The reason for this has quite simply got to be that one need a tremendous amount of experience to use what Developer Express has to offer. The reason for this is not that our controls are inordinately difficult to use, rather it is that there is a tremendous amount of pre-existing work available for the picking if one knows where to look. Experience is how one knows where to look.

    I have spent the better part of 1,500 hours this year working on Professional DevExpress ASP.NET Controls—the book—and am in awe of how much material is added regularly to our product line. The ASP.NET controls is a big part of our product line—by no means all—but there is a lot there. In retrospect I think we could have written an ASPxGridView book and easily covered 400 pages. It was actually hard to keep the grid coverage at 100 pages. The ASPxScheduler is another big control. In fact, the ASPxScheduler is more of a sub-system than a control. With a little bit of code and a couple of data source you could create an MS Outlook (tm) style interface, a project management tool, or just about any scheduler you could imagine (I suspect). And, you could probably create a substantial amount of functionality without writing hardly any code at all. The caveat being that you know how to use the control.

    One of my objectives as evangelist is to give you the information you need to leverage as much of the code we have already written for you without you having to re-write from scratch what more than likely already exists—if you just know where to look. To that end, me, Mehul, Oliver, Gary, Mark and many others write blogs, tweets, articles (and a book) now to point you in the right direction. I use custom objects a lot for development, and this blog post shows you how to use the ASPxScheduler with custom objects. (You can of course use a database, but that’s another blog post and its covered in the book.) Here is an overview of what it takes to use custom objects with the ASPxScheduler:

    • The ASPxScheduler normalizes appointment item as the appointment (as well as recurring appointment detail) from one source and resources from another
    • The ASPxScheduler separates presentation from Storage with pre-determined fields for appointments and resources
    • You simply map your appointment fields to our appointment fields and your resource fields to our resource fields, specify how the CRUD happens—SQL or whatever—and the scheduler is fully operational

    So for custom objects with the ASPxScheduler you need the custom objects, a BindingList<T>, and the equivalent of an object data source class, and if your custom object fields use dissimilar names then you need to map your properties to the scheduler’s properties.  it sounds like a lot of code, but the example can actually be coded—custom objects, lists, and data source—with just under 200 lines of code, most of which is represented by your custom objects. And, if you use CodeRush or automatic properties this can be done very quickly.

    Reviewing the Appointment and Resources Schemas

    The “schemas” for the Appointment and Resources objects as the ASPxScheduler sees the world are represented by Figures 1 and 2 respectively. the closer your objects reflect these schemas the easier the mappings are. for example, if your appointment class has a StartDate named StartDate then it is a straight forward map. If you are using the mapping wizard it will guess. the closer the names and data types are the better the guesses will be.

    image
    Figure 1: The ASPxScheduler’s view of an appointment.

    image
    Figure 2:  The ASPxScheduler’s view of the resources world.

    For resources the demo simply uses an array of random names and the index as the ResourceId and ResourceName. You can get away with this because it isn’t necessary to map all of the fields. You can just specify the ones you need. The Appointments will be mapped to the custom object DentalAppointment (see Listing 1). DentalAppointment is defined using automatic properties and has a one-to-one mapping between its properties and the fields an Appointment uses in the ASPxScheduler (except for one extra field Price, which goes unused).

    Listing 1: The DentalAppointment class has almost the identical set of properties and types as the Appointments used by the ASPxScheduler, making mapping pretty easy.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    [Serializable]
    public class DentalAppointment
    {
      public object Id { get; set; }
      public DateTime StartTime{ get; set; }
      public DateTime EndTime{ get; set; }
      public string Subject{ get; set; }
      public int Status{ get; set; }
      public string Description{ get; set; }
      public long Label{ get; set; }
      public string Location{ get; set; }
      public bool AllDay{ get; set; }
      public int EventType{ get; set; }
      public string RecurrenceInfo{ get; set; }
      public string ReminderInfo{ get; set; }
      public object OwnerId{ get; set; }
      public double Price{ get; set; }
      public string ContactInfo{ get; set; }
    }

     

    Implementing the Custom BindingList<T>

    The most common in-memory holder for custom types is some kind of collection or collection<T>. The BindingList<T> is a good candidate for custom collections because it is a generic collection and it supports data binding.  Because BindingList<T> is a generic collection it can be instantiated with any type for the teplate parameter T. The BindingList<T> supports data binding because it has methods and events that are typical of databound operations, including AddNew, CancelNew, InsertItem, RemoveAt, OnAddingNew, etc. (You can check the help for the complete list of operations.) Listing 2 contains the DentalAppointmentList class, that inherits from BindingList<DentalAppointment>, for the sample.

    Listing 2: The generic data-bindable collection.

    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Linq;
    using System.Web;
    using System.ComponentModel;

    [Serializable]
    public class DentalAppointmentList : BindingList<DentalAppointment>
    {
      public void AddRange(DentalAppointmentList events)
      {
        Array.ForEach(events.ToArray(), e => this.Add(e));
      }
      public int GetEventIndex(object eventId)
      {
        var result = this.Where(item => item.Id == eventId).First();
        if (result != null)
          return this.IndexOf(result);

        return -1;
      }

    }

     

    Generally you can just instantiate Bindinglist<T>, but I use inheritance to create a place to add a few additional business behaviors. In our example AddRange was added to supported appending a list of items, and GetEventindex finds an object by its Id instead of by the object itself. AddRange and GetEventsIndex both use one of the newer features in .NET Lambda expressions--e=>this.Add(e) and item=>item.Id == eventId, respectively. A Lambda expressions is an even more condensed version of an anonymous method. The left side is like the function header and parameters and the right side is the method body. The funny operator => (referred to as “goes to” or a “gosinta” by me) is borrowed from the original mathematical expression language.

    You could use a plain vanilla for loop for both methods, but where is the fun in that.

    Implementing the Custom DataSource

    The custom datasource plays the role of negotiating CRUD behaviors between the client control—the ASPxScheduler in this case—and an ObjectDataSource component. The custom datasource—DentalAppointmentDataSource in Listing 3—will be the TypeName property of an ObjectDataSource. The ObjectDataSource component will be assigned to the ASPxScheduler’s AppointmentDataSource property. (Remember that the ASPxScheduler has an AppointmentDataSource and ResourceDataSource.)

    The key to a custom datasource is to define methods that are assigned to the ObjectDataSource’s Selectmethod, UpdateMethod,, InsertMethod and DeleteMeethod properties. The ObjectDataSource will forward these operations to the business object’s methods defined by ObjectDataSource.TypeName that perform these functions. Referring to Listing 3 it is evident by name which behaviors implement the required operations.

    Listing 3: A custom business object that  performs CRUD operations for a custom ObjectDataSource.

    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Linq;
    using System.Web;

    public class DentalAppointmentDataSource
    {
      private DentalAppointmentList events;
      public DentalAppointmentList Events
      {
        get { return events; }
        set { events = value; }
      }

        public DentalAppointmentDataSource(DentalAppointmentList events)
        {
        this.events = events;
        }

      public DentalAppointmentDataSource()
        : this(new DentalAppointmentList())
      {}

      #region ObjectDataSource methods
      public void InsertMethodHandler(DentalAppointment appointment)
      {
        Events.Add(appointment);
      }

      public void DeleteMethodHandler(DentalAppointment appointment)
      {
        Events.Remove(appointment);
      }

      public void UpdateMethodHandler(DentalAppointment appointment)
      {
        int eventIndex = Events.GetEventIndex(appointment.Id);
        if (eventIndex >= 0)
        {
          Events.RemoveAt(eventIndex);
          Events.Insert(eventIndex, appointment);
        }
      }

      public IEnumerable SelectMethodHandler()
      {
        DentalAppointmentList result = new DentalAppointmentList();
        result.AddRange(Events);
        return result;
      }
      #endregion
    }

    Defining the GUI

    The presentation for the demo is an ASPxScheduler bound to an ObjectDataSource who’s behaviors are supported by the custom business object DentalAppointmentDataSource (in Listing 3). To implement the GUI—shown at runtime in Figure 3—follow these steps:

    1. Place an ASPxScheduler on a Web form
    2. Right click the Smart tags menu, select Appointment Data Source|New data source
    3. In the Data Source Configuration Wizard select Object as the data source type
    4. Click OK
    5. Choose the DentalAppointmentDataSource as the business object
    6. Click Next
    7. In the Define Data methods step pick the relevant method for each of the SELECT, UPDATE, INSERT, and DELETE operations (see Figure 4)
    8. Click Finish
    9. Clear the ASPxScheduler’s AppointDataSourceID from the properties window because in this example we will be setting the DataSource and data binding in the code behind

     

    image
    Figure 3: The running demo application with its default appearance and one appointment added by me. 

     

    image
    Figure 4: Define the data source’s various CRUD operations.

    The numbered steps above will generate (minus a few bits we still have to do) the ASPX shown in Listing 4. With just a few lines of code more you will have a functioning appointment/scheduling sub-system.

    Listing 4: The ASPX generated by adding the ASPxScheduler and the ObjectDataSource.

    <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

    <%@ Register assembly="DevExpress.Web.ASPxScheduler.v9.1, Version=9.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.Web.ASPxScheduler" tagprefix="dxwschs" %>
    <%@ Register assembly="DevExpress.XtraScheduler.v9.1.Core, Version=9.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.XtraScheduler" tagprefix="dxschsc" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <dxwschs:ASPxScheduler ID="ASPxScheduler1" runat="server"
                onappointmentinserting="ASPxScheduler1_AppointmentInserting"
                Start="2009-06-30">
    <Views>
    <DayView><TimeRulers>
    <dxschsc:TimeRuler></dxschsc:TimeRuler>
    </TimeRulers>
    </DayView>

    <WorkWeekView><TimeRulers>
    <dxschsc:TimeRuler></dxschsc:TimeRuler>
    </TimeRulers>
    </WorkWeekView>
    </Views>
            </dxwschs:ASPxScheduler>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
                DataObjectTypeName="DentalAppointment" DeleteMethod="DeleteMethodHandler"
                InsertMethod="InsertMethodHandler"
                SelectMethod="SelectMethodHandler"
                TypeName="DentalAppointmentDataSource" UpdateMethod="UpdateMethodHandler"
                onobjectcreated="ObjectDataSource1_ObjectCreated">
            </asp:ObjectDataSource>
        </div>
        </form>
    </body>
    </html>

    Writing the Code-Behind

    The code behind performs five general tasks: data binding, populating the ASPxScheduler’s Resources, storing the DentalAppointmentList in Session, mapping the DentalAppointment properties to the ASPxScheduler appointment fields, and creating a unique appointment id when an appointment item is inserted in the scheduler. Listing 5 contains the code-behind—which is less than 100 lines of code—for the Web page, followed by a brief synopsis of the tasks being performed.

    Listing 5: The code-behind for the  sample application.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using DevExpress.Web.ASPxScheduler;
    using DevExpress.XtraScheduler;
    using DevExpress.Data;
    using System.Diagnostics;

    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
          MapCustomObjectsToAppointmentFields();
          PopulateResourcesData(Storage, 5);
          ASPxScheduler1.AppointmentDataSource = ObjectDataSource1;
          ASPxScheduler1.DataBind();

        }

        ASPxSchedulerStorage Storage { get { return ASPxScheduler1.Storage; } }

        private void MapCustomObjectsToAppointmentFields()
        {
          ASPxAppointmentMappingInfo mappings = Storage.Appointments.Mappings;
          Storage.BeginUpdate();
          try
          {
            mappings.AllDay = "AllDay";
            mappings.AppointmentId = "Id";
            mappings.Description = "Description";
            mappings.End = "EndTime";
            mappings.Label = "Label";
            mappings.Location = "Location";
            mappings.RecurrenceInfo = "RecurrenceInfo";
            mappings.ReminderInfo = "ReminderInfo";
            mappings.ResourceId = "OwnerId";
            mappings.Start = "StartTime";
            mappings.Status = "Status";
            mappings.Subject = "Subject";
            mappings.Type = "EventType";
          }
          finally
          {
            Storage.EndUpdate();
          }
        }

        public static string[] Users = new string[] { "Peter Dolan", "Ryan Fischer", "Andrew Miller", "Tom Hamlett",
                            "Jerry Campbell", "Carl Lucas", "Mark Hamilton", "Steve Lee" };
        private void PopulateResourcesData(ASPxSchedulerStorage storage, int count)
        {
          ResourceCollection resources = storage.Resources.Items;
          storage.BeginUpdate();
          try
          {
            int c = Math.Min(count, Users.Length);
            for (int i = 1; i <= c; i++)
              resources.Add(new Resource(i, Users[i - 1]));
          }
          finally
          {
            storage.EndUpdate();
          }
        }
        private DentalAppointmentList GetDentalAppointments()
        {
          DentalAppointmentList events =
            Session["ListBoundModeObjects"] as DentalAppointmentList;
          if (events == null)
          {
            events = GenerateDentalAppointmentEventList();
            Session["ListBoundModeObjects"] = events;
          }
          return events;
        }

        private DentalAppointmentList GenerateDentalAppointmentEventList()
        {
          return new DentalAppointmentList();
        }

        protected void ASPxScheduler1_AppointmentInserting(object sender, PersistentObjectCancelEventArgs e)
        {
          ASPxSchedulerStorage storage = (ASPxSchedulerStorage)sender;
          Appointment appointment = (Appointment)e.Object;
          storage.SetAppointmentId(appointment, "d" + appointment.GetHashCode());

        }
        protected void ObjectDataSource1_ObjectCreated(object sender, ObjectDataSourceEventArgs e)
        {
          e.ObjectInstance = new DentalAppointmentDataSource(GetDentalAppointments());
        }
    }

    The Page_Load method performs initialization by performing calling the mapping step, adding resources, and data binding. The Storage property is a simplifcation property that simply surfaces the underlying ASPxScheduker’s Storage property. MapCustomObjectsToAppointmentFields does exactly what the method name indicates: it tells the ASPxScheduler what names in the custom objects refer to which fields in the scheduler. (The name-strings are case sensitive.) PopulateResourcesData creates some resources, in this case, random names playing the role of Dentist. GetDentalAppointmentList manages creating the events list and storing it in Session. GenerateDentalAppointmentList is really just a placeholder. In a real business application this method would ultimately construct appointments from a data source like a Web service, XML file, or database. AppointmentInserting is event that needs to be created on the ASPxScheduler.This method sets the AppointmentId, cleverly using an object’s guaranteed to be unique hash code. Finally, ObjectCreated.ObjectCreated is associating the custom datasource with the ObjectDataSource.

        protected void ASPxScheduler1_AppointmentInserting(object sender, PersistentObjectCancelEventArgs e)
        {
          ASPxSchedulerStorage storage = (ASPxSchedulerStorage)sender;
          Appointment appointment = (Appointment)e.Object;
          storage.SetAppointmentId(appointment, "d" + appointment.GetHashCode());

        }
        protected void ObjectDataSource1_ObjectCreated(object sender, ObjectDataSourceEventArgs e)
        {
          e.ObjectInstance = new DentalAppointmentDataSource(GetDentalAppointments());
        }
    }

    The ASPxScheduler peforms a large number of housekeeping tasks, including adding user controls for editing appointments, searching and recurring appointments. While it takes some work to get the scheduler up and running, the work—represented by this article—is infinitesimally smaller than the amount of code needed to implementing a custom scheduler control.

    Thanks for playing.

More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.