in

ASP.NET Weblogs

Joe Wrobel

  • Conditional Formatting in the Silverlight DataGrid

    I’ve been an asp.net developer for some time now and I was excited to jump on to Silverlight when it 2.0
    was released a few months ago.  One thing I really struggled with was applying conditional formatting to
    the individual cells in the DataGrid control.  Coming from an asp.net background, I carried a lot of
    assumptions with me (big mistake).  I thought I could get a hold of the rows or cells collection and have
    my way with it, but no such luck.  I stumbled down several paths which all ultimately lead to dead ends. 
    After killing hours (maybe days) on trying to figure this out, I had to let it go and move on.  Now a month
    later I decided to give it another shot and I finally got it.  The answer was right in front of me all along. 
    I knew about the IValueConverter interface, but I didn’t fully understand its capabilities.  I thought it was
    only used for converting an object into a text representation or vice versa.  Actually, you can return
    anything you want from it.  So you can return a Button, Grid, or whatever.

    Another aspect I couldn’t figure out was how to get access to page members from within the Convert
    method.  For example, I wanted to render a button in the cell and wire up the button’s click event to a
    method in the page.  Sure, you could do this to a certain extent using templates, but then I couldn’t
    find a way to change the template conditionally based on a value in the bound data item.

    The solution I came up with was to create a delegate along with a class which implemented the
    IValueConverter interface and exposed two events.  One for converting and the other for converting
    back.  I can then declare this converter in the resources collection and setup a handler in the page
    as shown below.

    <UserControl.Resources>
    <local:UniversalConverter x:Key="ageConverter" Converting="ConvertAge" />
    <local:UniversalConverter x:Key="nameConverter" Converting="ConvertName" />
    </UserControl.Resources>

     

    Here is the markup i used in the “First Name” column of the DataGrid.

    <data:DataGridTemplateColumn Header="First Name">
    <data:DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
    <ContentControl Content="{Binding Converter={StaticResource nameConverter}}"
    HorizontalContentAlignment="Stretch"
    VerticalContentAlignment="Stretch" />
    </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
    </data:DataGridTemplateColumn>

     

    Here is the “ConvertName” method I wired up from the converter defined in the resources collection.

       1:  private object ConvertName(object value, Type targetType, 
    object parameter, CultureInfo culture) {
       2:      Employee employee = value as Employee;
       3:      if (employee == null) {
       4:          return value;
       5:      }
       6:   
       7:      if (employee.FirstName.Contains('a')) {
       8:          Button btn = new Button();
       9:          btn.Content = employee.FirstName;
      10:          btn.Click += ((sender, e) => {
      11:            HtmlPage.Window.Alert(
      12:              string.Format(
    "There is a button here because \"{0}\" contains an \"a\".",
      13:              employee.FirstName));
      14:          });
      15:   
      16:          return btn;
      17:      }
      18:      return new TextBlock {Text = employee.FirstName};
      19:  }

     

    This is the IConverter class and delegate I created to handle the conversions.

       1:  public class UniversalConverter : IValueConverter {
       2:   
       3:      public object Convert(object value, Type targetType, 
    object parameter, CultureInfo culture) {
       4:          return this.OnConverting(value, targetType, 
    parameter, culture);
       5:      }
       6:   
       7:      public object ConvertBack(object value, Type targetType, 
    object parameter, CultureInfo culture) {
       8:          return this.OnConvertingBack(value, targetType, 
    parameter, culture);
       9:      }
      10:   
      11:      protected object OnConverting(object value, Type targetType, 
    object parameter, CultureInfo culture) {
      12:          UniversalConverterHandler handler = this.Converting;
      13:          if (handler != null) {
      14:              return handler(value, targetType, parameter, culture);
      15:          }
      16:          return value;
      17:      }
      18:   
      19:      protected object OnConvertingBack(object value, Type targetType, 
    object parameter, CultureInfo culture) {
      20:          UniversalConverterHandler handler = this.ConvertingBack;
      21:          if (handler != null) {
      22:              return handler(value, targetType, parameter, culture);
      23:          }
      24:          return value;
      25:      }
      26:   
      27:      public event UniversalConverterHandler Converting;
      28:   
      29:      public event UniversalConverterHandler ConvertingBack;
      30:   
      31:  }
      32:   
      33:  public delegate object UniversalConverterHandler(object value, Type targetType, 
    object parameter, CultureInfo culture);

     

    And finally, here is a screenshot of the DataGrid. (Ugly, I know, but it proves my point.)
    image

     

    Here is a link to the complete solution.

    I hope you find this useful and I look forward to hearing feedback and suggestions. 
    Most importantly, let me know if you know of a better way to do this.

    Thanks
    -Joe

  • Web Profile Builder 1.3

    I released a new build of Web Profile Builder.
    Not much has changed for the 1.3 release.  The focus of this release was to revert an undesirable change I made for version 1.2.  I did however add one new feature so I changed the version to 1.3.  What was the new feature added?  I changed the generated profile class to be a partial class.  Now you can easily add any custom code to the profile class (in a separate code file) without losing your changes when the profile class gets regenerated.

    Files can be downloaded from the MSDN Code Gallery here.

    Thanks
    -Joe

  • Web Profile Builder 1.2.0.0 Released

    Files can be downloaded from the Web Profile Builder project page.
    If you are unfamiliar with Web Profile Builder, you can read my initial blog post about it here.
    Details of this release can be found on the Code Gallery web site.
    I had a little time to put together some basic documentation.  It is available from the release download section.

    Thanks
    -Joe

  • Web Profile Builder 1.1.0.0 Released

    Files can be downloaded from the Web Profile Builder project page.

    If you are unfamiliar with Web Profile Builder, you can read my initial blog post about it here.

    Changes made for release 1.1.0.0:

    • Added the ability to detect changes made to the profile section of the web.config file
      and only rebuild the Profile class if changes have been made.

    Notes:

    • If you used the previous release, remember to uninstall it first.
    • Also, if you used the previous release and added the customize section in the
      web.config file, remember to update the assembly reference to
      “WebProfileBuilder.WebProfileConfigurationSection, WebProfileBuilder, Version=1.1.0.0,
      Culture=neutral, PublicKeyToken=01d50f1f82943b0c”.

    Thank you to everybody who provided me with valuable feedback.  This release should address
    all of your concerns.

    Thanks
    -Joe

  • ClientID Problem In External JavaScript Files Solved

    Well, at least for me it is.  I say that because this solution might
    not appeal to the masses, but it works great for me.

    The binary and source files can be downloaded from the MSDN Code Gallery.
    Here is the direct link.
    http://code.msdn.microsoft.com/RegClientControls

    Up until lately, I have been writing my JavaScript in the .aspx file.
    That way I could use the ClientID trick.  <%=TextBox1.ClientID%>
    I just began working with a team who prefers to write all JavaScript in
    external .js files.  What they had been doing is hard coding the
    ClientID prefixes caused by the container controls.  I guess this works
    fine.  It has been working for them so far.  I personally can't do this.
    It just feels wrong.

    I took some time to figure out a better way to deal with the ClientID in
    an external JavaScript file.  I found an interesting article
    here about
    Creating JavaScript objects from ASP.NET objects.  I liked David's technique,
    but it still required manually writing JavaScript on the .aspx page. I wrote
    a control called "RegClient" that encapsulates and automates this technique.
    If you place this control on your page anywhere below the Script Manager,
    then all you have to do to access the controls from JavaScript is something
    like "var controlObj = PageControls.TextBox1;".

    The RegClient control is dependant on Microsoft ASP.NET AJAX 1.0.  The
    source could easily be targeted to .NET 3.5 if you wanted.  I'm sure
    that with a little work it could even be library independent, but I
    didn't have that requirement.

    The RegClient control has two modes, "Marked" and "All".

    Marked:
    With the RegClient control set to Marked, only the controls marked with
    RegClient="true" or ClientControlID="{yourClientName}" will be registered.

    <Robo:RegClient ID="RegClient1" runat="server" ClientControls="Marked" />

    All:
    With the RegClient control set to All, every control in master
    and content pages will be registered unless RegClient="false".

    <Robo:RegClient ID="RegClient1" runat="server" ClientControls="All" />

    To access the controls in the external JavaScript file, handle the
    "ready" event of
    the "PageControls" object like shown here.
    PageControls.add_ready(function(){
       
    //Write your code here.
    });

    All of the controls that have been registered will be available from
    the "PageControls" object in JavaScript.  Here is an example of accessing
    the controls.

    PageControls.add_ready(function(){ 
        $addHandler($get('Button2'), 'click'
            function() { 
               
    PageControls.Label1.innerHTML = PageControls.tbText.value; 
           

       
    ); 
    });

    One caution I would make for using this control is to be careful when you
    have the ClientControls setting to "All".  This control will find and
    register every control that derives from WebControl.  If you're using
    a GridView or something similar on the page, then there could be many
    extra controls that you don't want to get registered.

    Here is a screen shot of the RegClient control.  There really is only one
    setting for this control, but I added a big blob of text as a reminder of
    how to use it with other controls.

    image

    When the page renders, this is what the generated output looks like at
    at the bottom of the page.

    image

    So, that's it.  I hope you find this useful and if you know of any ways
    this control could be improved, please let me know.

    Thanks
    -Joe

  • Web Profile Builder for Web Application Projects

    Files can be downloaded from the Web Profile Builder project page.

    If you use Web Application Projects, you have probably run into the issue of not being
    able to access the Profile at design time.

    Thankfully, some nice people created an Add-In for Visual Studio 2005 that will generate
    a wrapper class as a workaround.  That project can be found here.  I wanted to contribute
    to the Web Profile Generator project, but my emails went unanswered.  I decided to start
    a new project.

    This project picks up where that one left off and is based on their source code.  I started
    out rebuilding that project as it stood to add support for Visual Studio 2008.  I got it to
    work, but I wanted to do more. 

    I decided to go ahead and address the issues listed on the original project site.

    Here is a summary of the changes made.

    • First of all, this is no longer an Add-In.  Instead, it is a Build Task.
    • Works for Visual Studio 2005 and 2008.
    • Added ability to set the file name.
    • Added ability to set the directory the file gets created in.
    • Added ability to set the class name.
    • Added ability to set the name space.
    • Added an extra method as requested here.

    Usage:

    1. Run the installer.
    2. Add this Import statement to your project file.  (see special notes below)
      <Import Project="$(MSBuildExtensionsPath)\WebProfileBuilder\WebProfileBuilder.targets" />
    3. Done.

    The profile will get generated every time you build the project.

    Special Notes:

    After you modify the project file by adding the import statement, you will get this security
    warning when the project loads.  Choose the "Load project normally" option and press OK.
    image 

    Extended Usage:

    If you want to customize the web profile, you can add the following sections to your web.config.

    This section needs to be at the top of the file just under the opening configuration tag.
    <configSections>
       
    <sectionGroup name="robo.webProfile">
           
    <section name="webProfileSettings"
             
    type="WebProfileBuilder.WebProfileConfigurationSection, WebProfileBuilder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=01d50f1f82943b0c"
             
    allowLocation="true"
             
    allowDefinition="Everywhere"/>
       
    </sectionGroup>
    </configSections>

    This section can be anywhere under the configuration section.
    <robo.webProfile>
       
    <webProfileSettings className="MyWebProfile"
                           
    nameSpace="CustomNameSpace" 
                           
    directory="CodeFiles"
                           
    fileName="MyWebProfile" />
    </robo.webProfile>

    The directory name is not a virtual directory, but is in reference to the root of the Web Application.
    The fileName is just the name of the file and should not include an extension.
    The className and nameSpace are as you would expect.
    None of these settings are required.  You don't even need to define this section at all.

    If you would like to use Web Profile Builder, it can be downloaded here.
    Also available is the complete source code and examples in VB and C#.

    Thanks
    -Joe

  • A more elegant solution to display GridView header and footer when the data source is empty.

    I think the need to always show the header and footer of a GridView is pretty common.
    When I first ran into this problem, I went to Google and found lots of content about this.
    Some suggest tampering with the data source to add an extra row if it’s empty. 
    Others show overriding the CreateChildControls method.

    I was not satisfied with either of these solutions. I didn’t like that dirty feeling I had by tampering
    with the data source.  And I didn’t like overriding the CreateChildControls method because it
    simply didn’t work of me anyway.  This solution only gave the appearance of the header and
    footer existing.  I ran into issues because I was programmatically adding controls to the header. 
    Upon postback, if the data source was empty, the control hierarchy would not be the same as
    before thus causing an error.

    So here is my solution.  I'll cover the main points of interest and if you want to see more, I have
    uploaded all the source and example to the new MSDN Code Gallery.
    http://code.msdn.microsoft.com/AlwaysShowHeaderFoot

    Start out by extending the GridView control and add the following structure. 
    The most important part here is to override the PerformDataBinding method.
     ClassStructure

    As you can see here.  I am intercepting the data and making sure there is at lease one row. 
    If there is, the control behaves normally.
    On the line with the red arrow, I am checking if the binding source is a DataView. 
    If it is, I can just add a row here and be done with it.
    If the binding source is not a DataView (or DataSet), then I fire an event that will need to be handled.
     image

    The event "MustAddARow", as seen here, will provide access to the binding data and allow you to add a row.
     image

    Here is a snippet from a page where I handle the MustAddARow event.
    In this case, I am binding a List of Products to the GridView.
    As you can see, I'm just adding a new Product to the list.  It doesn't matter what data you add here
    because it will get hidden in back in the GridView.
    image

    And finally back in the GridView, I override the OnDataBound method so I can hide that extra row.
    image 

    So there you have it.  This is my first blog post ever. 
    Hopefully someone will get some use out of this.

    Thanks
    -Joe

More Posts