sano the super geek

the endeavors of a professional geek at microsoft.

  • Microsoft Build Sidekick

    The folks at Trivium Technologies have released the first version of their MSBuild Sidekick, a GUI front end to MSBuild. Even though this tool probably cannot replace the productivity that I can currently attain by taking advantage of the Intellisense support in Visual Studio, I think it would have been a great asset when I was first starting out with MSBuild. It's also free, so there's no harm in checking it out.
  • Custom Windows Forms Controls: ColorPicker.NET, Part 2

    It took them a long time, but the folks at MSDN finally got around to publishing the second part of my ColorPicker.NET article. In this one, I cover the implementation details for the scrolling magnifying glass control and the zoom panel that is used to magnify the captured portion of your desktop.

    You can get the source code for v.0.2 from the ColorPicker.NET workspace. I haven't had the chance to recompile it in .NET v.2.0 yet, although I do have plans to continue developing the tool on that platform.

  • Changing the default sort order of your sortable columns on the ASP.NET GridView

    One of the great things about the new ASP.NET GridView control is that it allows you to implement column field sorting with relative ease, at least in comparison to what you would have had to do to attain similar functionality in earlier versions. There are a plethora of articles and tutorials out there that lead you through this process, but one thing that most, if not all, of these are lacking is some kind of hint of how to override the default sorting order of your sortable columns. Since this was a problem that I had to solve recently and given the number of times I’ve seen the question being asked (and unanswered) across various forums and newsgroups, I figured I’d take some time to share my solution with the community.

    This entry assumes that you have a good understanding of how sorting works in the DataGrid. If you don’t, I would suggest that you take some time to do so before continuing. There is no download, but I provide enough code for you to be able to implement this solution on your own grid.

    The GridView control raises two sort-related events when the user clicks on a link button in the header of any columns that has its CommandName property set to Sort. The Sorting event is raised before the GridView handles the sort operation and the Sorted event is raised after the sorting has completed and data has been bound to the grid.

    The Sorting event is of particular interest to us. By handling this event, we have the opportunity to manipulate the sort data before the grid hands it to the underlying data store (usually managed by an instance of ObjectDataSource) and asks for the data to be manipulated in a form that conforms to the sorting rules that it has established.

    When the event is handled, we receive a reference to a GridViewSortEventArgs object which contains properties that contain values that we are interested in. The first is the SortExpression property, which, in the simplest of situations, contains the name of the field that is to be sorted. The second is the read-only SortDirection property which defines the order in which the data will be sorted. This actually serves no purpose in this space other than to provide assistance in identifying the eventuating sort direction.

    Once the handler has completed its scope of execution, if the sort has not been cancelled by setting the Cancel property of the GridViewSortEventArgs object to true, an instance of DataSourceSelectArguments is created and the final sort expression is defined. The pertinent value is essentially a concatenation of the SortExpression value and the SQL equivalent of the SortDirection value if it is defined as SortDirection.Descending. In other words, given a column named (and sort expression defined as) Rating and a SortDirection value of SortDirection.Descending, the ultimate sort expression will be “Rating DESC.” If the SortDirection value is SortDirection.Ascending, the expression would be restricted to only the defined SortExpression, which would be “Rating.”

    Now that we have attained this understanding, let’s take a look at how this can be manipulated to reverse the default sorting order.

    protected void SearchResultsView_OnSorting( object sender, GridViewSortEventArgs e ) {
      e.SortExpression = e.SortExpression + " DESC"
    }

    In the above code block, I’ve overridden the SortExpression value that was captured at the time that the event was raised. Instead of the values in the column being sorted in ascending order, which, again, is the default, the expression has been defined as a concatenation of the original sort expression and DESC. Once the event has been handled, the updated SortExpression will be parsed and the SortDirection property on the grid will be updated as appropriate to match the order that you specified in the expression. What this means is that if the next sort is done on the same column, the SortDirection value will be SortDirection.Ascending instead of SortDirection.Descending.

    The code would work great if you wanted the default (and only) sorting order of each of the sortable columns in your grid to be descending. This is clearly not the desirable outcome. Since we only want this exception to apply to the Rating expression, we’ll add some code that checks for this expression before overriding its default sort.

    protected void SearchResultsView_OnSorting( ... ) {
      if ( e.SortExpression.Equals( "Rating" ) ) {
        e.SortExpression = e.SortExpression + " DESC";         
      }
    }
    

    We still want the contents of the Rating column to be sorted in ascending order, but only after the column has previously been sorted in descending order. To clarify, there are two different scenarios that we need to cover:

    1. Sorting the Rating column when it's already in descending order should result in an ascending sort.
    2. If a different column is sorted after the Rating column has been sorted, regardless of its order, the first sort of the Rating column afterwards should be descending.

    The implementation is actually relatively simple. All we need to do is stash away the most recent expression in a state bag (ViewState) and load and compare it when the next Sorting event is handled. The following code demonstrates how we first check to make sure that the current sort expression is the Rating expression, then we check the value stored in the PreviousSortExpression key in ViewState to see if it was used in the previous sort. If it was, then the sorting is done based on the current SortDirection, otherwise it's forced to descending order.

    protected void SearchResultsView_OnSorting( ... ) {
    
      string originalExpression = e.SortExpression;
    
      if ( e.SortExpression.Equals( "Rating" ) ) {
    
          if ( ViewState[ "PreviousSortExpression" ] == null || 
               !ViewState[ "PreviousSortExpression" ].ToString().Equals( originalExpression ) ) {
                
            e.SortExpression = e.SortExpression + " DESC";
            
          } 
    
       }
    
      ViewState[ "PreviousSortExpression" ] = originalExpression;
    
    }
    

    That does it. Happy Sorting!

  • Incorrect syntax near '[Insert Arbitrary Column Name]'

    I ran into a rather mysterious error when working with some SQL code today.
    Incorrect syntax near '[Insert Arbitrary Column Name]'

    Before I get to the nuts and bolts of the problem, we'll start with some background details. The stored procedure that I was working on was broken down into two disparate parts. The first part had two different queries encapsulated in try/catch blocks (also a new feature of SQL 2005 in case you weren't already aware). The query in the try block attempts to perform a full-text search using keywords that are provided through one of the stored procedure parameters. In the case of failure (i.e. if the keyword parameter is NULL), control in the try block is interrupted and restored in the catch block where a query is performed against the table, retrieving all of the records that exist. Matching records are stored in a temporary table for processing in the latter part of the procedure. In the second part, the records in the temporary table are filtered further according to various attributes that are defined through the procedure's parameter list. Several aggregations and computations also occur here. The

    I was attempting to leverage the new ROW_NUMBER() function while in the process of updating a stored procedure to provide custom pagination support for one of our DataGridViews. This function provides a means for providing a sequential series of row numbers for a partition of a result set. In other, much simpler terms, paging assistance. This allows you to conveniently define a range of results that you would like to retrieve from the data store. For example, the following SQL code retrieves the ID, first name, last name and row number of the first 10 records in the Person.Contact entity in the AdventureWorks database.

    SELECT TOP 10 ContactId, FirstName, LastName, ROW_NUMBER() OVER ( ORDER BY ContactId ASC ) AS RowNumber FROM Person.Contact WHERE MiddleName IS NOT NULL

    This results in the following table:

    ContactId FirstName LastName RowNumber
    1 Gustavo Achong 1
    2 Catherine Abel 2
    3 Kim Abercrombie 3
    4 Humberto Acevedo 4
    5 Pilar Ackerman 5
    6 Frances Adams 6
    7 Margaret Smith 7
    8 Carla Adams 8
    9 Jay Adams 9
    10 Ronald Adina 10

    Take note of how the RowNumber column lists integers in sequential order pertaining to the designated ordering of the ContactId column of the individual entities of the Person.Contact entity (ROW_NUMBER() OVER ( ORDER BY ContactId ASC )). It's not obvious in the table above, but because the numbers in RowNumber are sequential, it becomes even more simpler to define a range of records that you'd like returned without having to worry about mangling your code with convoluting logic that attempts to define a range using non-sequential identity values. (Think about how you would define a range if ContactIds 3 and 7 were deleted -- this is discussed later on).

    The BETWEEN operator helps you define a range. It requires that you supply a set of two operands, one of which defines the column where the data resides and the other which defines the lower and upper bound of the range that will be defined by the operator when the statement is executed. A very basic example showing how you can retrieve the records of contacts whose ContactID is between 4 and 7 follows:

    SELECT ContactId, FirstName, LastName, ROW_NUMBER() OVER ( ORDER BY ContactId DESC ) AS RowNumber FROM Person.Contact WHERE ContactId BETWEEN 4 AND 7

    ContactId FirstName LastName RowNumber
    4 Humberto Acevedo 1
    5 Pilar Ackerman 2
    6 Frances Adams 3
    7 Margaret Smith 4

    You can now see that the ContactId and RowNumber values are no longer in sync. Again, this is because the row numbers are generated sequentially based on a partition of the greater result set. In this case, the partition is the range of records in which the ContactId value is between 4 and 7 inclusive.

    So, back to the issue that I encountered. After writing some sample code, it was clear that in order to define a range, I would need to use the BETWEEN operator, providing the derived RowNumber column and my desired range. It sounded simple enough, so I wrote the following code to evaluate my understanding of the potential solution.

    SELECT 
      ContactId,
      FirstName,
      LastName,
      ROW_NUMBER() OVER ( ORDER BY ContactId ASC ) AS RowNumber
    FROM Person.Contact
    WHERE RowNumber BETWEEN 1 AND 5

    When I executed the code, I encountered the error message mentioned at the beginning of this entry.

    Invalid column name 'RowNumber'.

    I was dumbfounded. The error message was of absolutely no assistance. This was one of those moments where I just knew that my inexperience with SQL would result in a really painful (but eventually beneficial) journey. I traversed several different pages of the documentation in an attempt to figure out why what seemed like a relatively simple SQL query had gone haywire on me. My office mate jumped in after a while and after several different searches, was able to find something that stated that because RowNumber was a derived column, it could not be referenced in the search expression. I thought that this was kind of weird, but assured myself that it made sense. The next step was to try and figure out a way to use the values generated by ROW_NUMBER() to define a range. 

    I decided to continue working with the Persons.Contact entity in the AdventureWorks database, writing script that emulated the stored procedure that I was attempting to modify. First, I needed to create a temporary table that would house preliminary results, just like the table in my stored procedure that contains the results of the full-text search.

    DECLARE @Results TABLE(
      ContactId int,

      FirstName nvarchar(50),

      MiddleName nvarchar(50),

      LastName nvarchar(50
    )
    )

    I then wrote the query that would populate the temporary table with data. This is relatively similar to the query that I used earlier with the addition of the MiddleName column which is used to refine the result set in the next query.

    INSERT INTO @Results
    SELECT TOP 10

      ContactID,
      FirstName,

      MiddleName,

      LastName

    FROM Person.Contact

    The final query returns a result set containing contacts that have middle names.

    SELECT
      ContactId,
      FirstName,

      MiddleName,

      LastName

    FROM
    @Results
    WHERE
    MiddleName IS NOT NULL

    My initial approach to the problem was altering the temporary table to add a RowNumber field and populate this column in the initial query as such:

    INSERT INTO @Results
    SELECT

      ContactID,
      FirstName
    ,

      MiddleName
    ,

      LastName
    ROW_NUMBER() OVER ( ORDER BY ContactId ASC ) AS RowNumber
    FROM Person.Contact

    ContactId FirstName MiddleName LastName RowNumber
    1 Gustavo NULL Achong 1
    2 Catherine R. Abel 2
    3 Kim NULL Abercrombie 3
    4 Humberto NULL Acevedo 4
    5 Pilar NULL Ackerman 5
    6 Frances B. Adams 6
    7 Margaret J. Smith 7
    8 Carla J. Adams 8
    9 Jay NULL Adams 9
    10 Ronald L. Adina 10

    It was immediately obvious that I was going to have a problem after I ran my second query. Take another look at the query. Notice how I'm filtering by records that contain middle names (or at least initials). When this second query runs, I'm given a result set that contains row numbers that are out of order.

    ContactId FirstName MiddleName LastName RowNumber
    2 Catherine R. Abel 2
    6 Frances B. Adams 6
    7 Margaret J. Smith 7
    8 Carla J. Adams 8
    10 Ronald L. Adina 10

    At this point, I was even more confused. I could think of a few different approaches that could work here (views, cross-applying a UDF, creating a second temporary table, et al), but was not sure which one would be the most beneficial performance-wise, especially when dealing with large volumes of data. I headed back to the documentation, this time focusing on derived columns, hoping to find some kind of hint that would explicate how to reference this type of column in a search expression. Right under derived columns in the indexed view was derived tables (or virtual tables). After reading some more on this, I discovered that derived tables are, in their simplest form, SELECT statements in the FROM clause that forms a table referred by an alias that can be used by the outer select. To me, this meant that I could move the ROW_NUMBER() logic from the first query to the second query and then utilize derived tables to access the value of RowNumber.

    SELECT * FROM (
      SELECT 
        ContactId,
        FirstName,

        MiddleName,

        LastName,

        ROW_NUMBER() OVER ( ORDER BY ContactId ASC ) AS
    RowNumber

      FROM @Results
      WHERE MiddleName IS NOT NULL
    )
    AS t

    ContactId FirstName MiddleName LastName RowNumber
    2 Catherine R. Abel 1
    6 Frances B. Adams 2
    7 Margaret J. Smith 3
    8 Carla J. Adams 4
    10 Ronald L. Adina 5

    I went back and modified my queries to return more results and verified that this was indeed the solution that I was looking for. With accurate sequential ordering, I was now able to define a range using the BETWEEN operator. I was now able to page through all of the instances of the Persons.Contact entity that contained a defined middle name. The complete set of SQL statements follows.

    USE AdventureWorks

    DECLARE @Results TABLE(
      ContactId int,

      FirstName nvarchar(50),

      MiddleName nvarchar(50),

      LastName nvarchar(50
    )
    )

    INSERT INTO @Results
    SELECT

      ContactID,
      FirstName
    ,

      MiddleName
    ,

      LastName

    FROM Person.Contact

    SELECT * FROM (
      SELECT 
        ContactId,
        FirstName,

        MiddleName,

        LastName,

        ROW_NUMBER() OVER ( ORDER BY ContactId ASC ) AS
    RowNumber

      FROM @Results
      WHERE MiddleName IS NOT NULL
    )
    AS t
    WHERE RowNumber BETWEEN 1 AND 15

    The stored procedure was updated with the code that I had come up with while testing out derived tables. Now we're able to provide a much more effective paging experience on our search page in which we are only retrieving the results that are being displayed as opposed to pulling all of the data and only displaying the appropriate partition. (OUCH).

  • Passing values between two IHttpHandlers within the same HttpContext

    My latest ASP.NET coding endeavor required that I perform a postback and then transfer control over to a different page (primarily for URL concealment purposes) using Server.Transfer. I needed to be able to pass values from the source page to the target page, but didn't really have a good way to do this. A quick gander at the documentation seemed to indicate that the way to do this was to define public properties on the source page, use the Handler property of the Context instance to retrieve a IHttpHandler-based reference to the source page which can then be cast to the source page type, providing you with access to all of the public properties, as shown in the following code block:

    MyPage page = ( MyPage ) Context.Handler;
    int num = page.MyPublicProperty;

    A MSN search (yes, really) revealed several different approaches that people have taken to persist data across multiple pages within the same HTTP context (which essentially is one round-trip to the server). One person encouraged the practice of declaring public dictionaries that are persisted in similar fashion as the code above, others sanctioned the use of server side (session) cookies and some went as far as defining hidden field values and accessing them through the HttpValueCollection exposed by HttpRequest.Form on the target page.

    I decided to take some time to go through all of the findings that I had come across and determine whether or not they would be of use. I've provided a brief summary of what I discovered as I went through each of the three different approaches that I found followed by my solution.

    Declaring Public Properties

    Declaring a public dictionary of values that I wanted to pass from the source page to the target page seemed to make sense, but I was still reluctant about exposing a public property in my Page class, especially when the only purpose that it would serve would be to pass this dictionary across connecting pages on the server. Additionally, this would create a strong coupling between pages that are involved in the transfer, which is something I really wanted to avoid. As an example, suppose I had three pages, A, B and C, each containing a Dictionary typed public property named Values, that were transferred to page Z. The code would look something like this:

    Dictionary<int, string> valueDictionary;

    if ( Context.Handler.GetType().Name.ToLower().Equals( "a_aspx" ) ) {
      valueDictionary = ( ( A ) Context.Handler ).Values;
    } else if
    ( Context.Handler.GetType().Name.ToLower().Equals( "b_aspx" ) ) {
      valueDictionary = ( ( B ) Context.Handler ).Values;
    } else if
    ( Context.Handler.GetType().Name.ToLower().Equals( "c_aspx" ) ) {
      valueDictionary = ( ( C ) Context.Handler ).Values;
    } else
    {
      // they shouldn't be here. redirect them somewhere else.

    }

    // do something with valueDictionary

    This kind of coupling would influence a required code change in Z if any of the following scenarios were to happen:

    1. I decided that I wanted to transfer page D to Z.
    2. I no longer wanted one or more of the pages to be transferred to Z.
    3. One of the pages is no longer needed and is deleted from the web project.

    Sessions

    We have a policy that prohibits the use of sessions, so that was not something that I could use. Even if I could, I wouldn't unless it was as a last resort. Sessions, especially on a high-traffic site, can be expensive and should generally be avoided. They are also pretty much useless in a web farm because there isn't a clean way to persist them across multiple server instances in the cluster unless you have a centralized session server.

    Hidden Form Fields

    The HttpValueCollection exposed by HttpRequest.Form provides access to a collection of key-value pairs in which the identities of the controls (from Control.ClientID) on the source page are contained. In the case of passing data by way of hidden fields comes the requirement that you be able to retrieve a reference to the control on the server. The best way to do this is to retrieve a IHttpHandler based reference to the source page, casting it to the appropriate page type, invoking the FindControl() method on the source page instance and retrieving the control by the identifier defined on the web form and accessing its value. This presents two distinct problems. First, by doing this, you're creating the same strong coupling dependency as you would by declaring public properties. In addition to that, you will have to ensure that each page that is transferred to this target page has a control with the given ID of choice. Secondly, you're breaking the rules of encapsulation by using FindControl(). This, in the eyes of many, is not recommended practice. Let's not forget the potential performance hit that you could take by forcing a traversal of the control hierarchy.

    Solution

    I was a little bit frustrated at this point. The first option seemed like the only reasonable option, but intuition pushed me towards the HttpContext class in the .NET Framework Class Library documentation. I figured that since I was operating within a HttpContext that the class might expose a collection of some sort that would allow me to define values that would persist throughout the HTTP request. I stumbled across the Items property which immediately piqued my interest. You couldn't go wrong with a description that says, "Gets a key/value collection that can be used to organize and share data between an IHttpModule interface and an IHttpHandler interface during an HTTP request" could you? A fury of finger movement upon my keyboard resulted in the following code:

    public partial class MyPage Page {

      protected override void OnLoad( EventArgs e ) {

        Context.Items[ "SomeKey" ] = 5; 
        Server.Transfer( "target.aspx",
    true );

      }

    }

    public partial class TargetPage Page {

      protected override void OnLoad( EventArgs e ) {

        if ( Context.Items[ "SomeKey" ] == null ) {
          // redirect
        }

        // use value of Context.Items[ "SomeKey" ]

      }

    }

    Mission accomplished. With this approach, the only thing that would need to be done in order to transfer a page to TargetPage would be to define the appropriate keys in the key/value collection exposed by Content.Items before initiating the transfer.

  • Windows Vista UX Guidelines

    Microsoft recently posted the preliminary Windows Vista UX guidelines.

    These guidelines contain information on What’s New in Windows Vista, design principles, guidelines for controls, text, windows, and aesthetics. They will help designers and developers create high quality, consistent Windows Vista applications.

  • XAMLShare

    I just came across XAMLShare, a public wiki dedicated to providing various Windows Presentation Foundation (Avalon) code samples. There aren't many samples at the time, but hopefully the number will grow as WPF works its way into the mainstream.
  • Getting away from Application.DoEvents

    Another great blog entry by Jessica Fosler: Keeping your UI Responsive and the Dangers of Application.DoEvents.
  • Wrapping text in DataGrid cells

    One of the most common questions I've been asked in recent weeks has been in regards to creating a data grid column style that supports text wrapping. It's really unfortunate that the DataGrid control does not provide this type of functionality, and even more that achieving a such effect is such a daunting task that requires a thorough comprehension of the control's rendering infrastructure. Since this was a requirement for one of the tools that I'm currently working on, I had no choice but to sit down and endure some pain in attempting to make this happen. I just finished up a very rough slab of code that produces what is visible in the following screenshot:

     

    Before you make a mad rush to download, please be cognizant that this is code in its crudest form. I'll update the sample as the tool proceeds through its stabilization phase. In the meantime, feel free to get in touch with me if there are any questions. Enjoy.

  • ColorPicker.NET v0.1.1

    I had a few cycles today so I spent some time fixing some bugs in ColorPicker.NET.

  • Transcripts for MSDN Designing .NET Class Libraries

    The transcripts for the MSDN Designing .NET Class Libraries class that Brad Abrams coordinated a few months back have finally been posted.

  • What the heck is this <UICulture> error and how do I fix it?

    If you've frustratingly encountered the "<UICulture> should be set in the project file in order to support the following markup files that use types defined in this project" error message in Avalon, Tim Sneath has come to the rescue with a temporary workaround for Beta RC1.

  • Emulating the OSX taskbar in Avalon

    Amir Khella shows how easy it is to create stunning visual effects in Avalon in his Playing Around with Avalon's Grid entry. Amazing!

  • Strings in .NET v2.0

    I just came across David Fetterman's article on MSDN covering the new recommendations for using strings in .NET v2.0. It's definitely worth a quick read.
  • ColorPicker.NET v0.2 preview

    I'm working on finishing up the code for the next release of ColorPicker.NET. The main focus of this release is on incorporating the screen pixel sampling functionality that so many people have requested. While working on the design, I decided that I wanted the user to do all of the color sampling from within the tool. This means that instead of being able to extend the cursor outside of the tool's boundaries to sample colors, you need to select the desired screen area using the available functionality and do all of the zooming and sampling inside the tool. The result can be seen in the following screenshot.

    The screen capture panel on the left side represents the area of the desktop that the user has selected. You can capture your preferred area by scrolling using the hot spots on the edges of the panel or by holding down the control key, clicking on the screen capture panel and dragging the translucent rectangle around the screen. As you drag, the area under the location of this rectangle will be displayed in the screen capture panel.

    Once you have captured the desired screen area, you will need to make a more specific selection. Do this by clicking inside the screen capture panel. This will activate what I call a zoom rectangle. This defaults to a zoom power of 1x. If you would like to zoom in further, select the desired zoom power from the magnifier control. Once you have zoomed in on the captured image, you can sample colors by clicking (and moving) within the bounds of the zoomed image panel on the upper right side of the control.

    As I mentioned earlier, I'm still working on finishing up, but I'd like some constructive criticism on the current implementation. Download the binaries and let me know what you think!

More Posts Next page »

© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker