Welcome to MSDN Blogs Sign in | Join | Help

Implementing multi-column filtering on the IBindingListView

A couple of years ago, I a whitepaper about how to implement searching and sorting on the generic BindingList for data binding in Windows Forms. About the same time my colleague, Karl, wrote a whitepaper about adding an auto-filter capability to the Windows Forms DataGridView control. Karl and I had talked about bridging the gap by me writing another whitepaper about how to implement filtering on a generic BindingList by implementing the IBindingListView. Well , it took a year or so but I did write the code and the whitepaper. During the process, I made one blog post about it, but continued to improve the code. Another few months have gone by, and the whitepaper is still not published, although I do have the paper and the code available on the Microsoft Download site. The download includes projects for Visual Studio 2005  and 2008 in both VB.NET and C#. The VS 2008 versions include some unit tests as well. I am a big fan of unit testing, even though I am by no means an expert. I found the unit tests very helpful and my editor suggested I publish them because you might find them helpful as well. In addition, I've included projects that demonstrate the filtered list working the the auto-filtered DataGridView.

When I started on this project I honestly did not understand how complex it was. When you implement filtering one of the first things you need to decide is what sort of filtering expressions your code will accept. I opted for a sub-set of the DataView.RowFilter format guidelines. I limited my operators to <,> and = to keep things simple. My idea was to give you a jumping off point for more complex filtering. In addition, I opted to only filter types that implement IComparable. Attempts to filter other types will result in an exception. A complication is that I started with a list that already supports single-column sorting. This means that the sorting and filtering implementations need to play nice together. For example, a sorted collection that is later filtered should remain in the same sorted order, or if a list is sorted and filtered, and the sort is removed, it should remain filtered.

Following is some of the code from the paper with a brief explanation. If you want to stop reading this post and go straight to the paper and code, you can find it here.

The are only three APIs exposed by IBindingListView that you need to implement for filtering:

  • SupportsFiltering property
  • Filter property
  • RemoveFilter method

SupportsFiltering is simple. For my implementation this always return true. The get for the Filter property is simple as well. I simply return the value of the backing private field. The filteret is where a lot of heavy lifting happens. When the Filter property is set, the filter is applied to the collection and only items meeting the filter criteria are returned. This means you need to do a lot of work in the set:

  • Cache the existing collection in case the filter is removed
  • Check for null or empty string; this means the filter has been removed and you need to restore the original collection
  • Parse the filter expression to determine if its in the correct format
  • Apply the filter, and return on list values that meet the filter criteria
  • Reapply the sort if necessary

These steps are further complicated by multi-column filtering.

Here's the code from my Filter set:

set
{
    if (filterValue == value) return;

    // If the value is not null or empty, but doesn't
    // match expected format, throw an exception.
    if (!string.IsNullOrEmpty(value) &&
        !Regex.IsMatch(value,
        BuildRegExForFilterFormat(), RegexOptions.Singleline))
        throw new ArgumentException("Filter is not in " +
              "the format: propName[<>=]'value'.");

    //Turn off list-changed events.
    RaiseListChangedEvents = false;

    // If the value is null or empty, reset list.
    if (string.IsNullOrEmpty(value))
        ResetList();
    else
    {
        int count = 0;
        string[] matches = value.Split(new string[] { " AND " },
            StringSplitOptions.RemoveEmptyEntries);

        while (count < matches.Length)
        {
            string filterPart = matches[count].ToString();

            // Check to see if the filter was set previously.
            // Also, check if current filter is a subset of
            // the previous filter.
            if (!String.IsNullOrEmpty(filterValue)
                    && !value.Contains(filterValue))
                ResetList();

            // Parse and apply the filter.
            SingleFilterInfo filterInfo = ParseFilter(filterPart);
            ApplyFilter(filterInfo);
            count++;
        }
    }
    // Set the filter value and turn on list changed events.
    filterValue = value;
    RaiseListChangedEvents = true;
    OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}

As I said previously, there's a lot happening here. First I check to see if the filter value has changed, if not, I simply return. Then I check for the correct filter format by calling BuildRegExForFilterFormat. Following is the code for this method:

// Build a regular expression to determine if
// filter is in correct format.
public static string BuildRegExForFilterFormat()
{
    StringBuilder regex = new StringBuilder();

    // Look for optional literal brackets,
    // followed by word characters or space.
    regex.Append(@"\[?[\w\s]+\]?\s?");

    // Add the operators: > < or =.
    regex.Append(@"[><=]");

    //Add optional space followed by optional quote and
    // any character followed by the optional quote.
    regex.Append(@"\s?'?.+'?");

   return regex.ToString();
}

If the filter is in the correct format I know I'm in business and turn off change notification so I can manipulate the contents of the list without notifying bound controls.

Next I check for null or an empty string. One of these values indicates the filter has been removed and I need to reset the contents of the list to the unfiltered content. Following is the code for the ResetList method. Once I restore the original list, I reapply the sort, if the list is sorted.

private void ResetList()
{
    this.ClearItems();
    foreach (T t in originalListValue)
        this.Items.Add(t);
    if (IsSortedCore)
        ApplySortCore(SortPropertyCore, SortDirectionCore);
}

Next I split the filter at "AND" to separate the parts of a multi-column filter. I loop through each filter part and parse and apply the filter to the list. I do some checking to see if the current filter part is a subset of a previous filter indicating a portion of a multi-column filter is being removed. In this case, I reset the list before I apply the filter. I created the SingleFilterInfo and FilterOperator enums to aid in parsing and filtering. The actual parsing and filtering are done by the ParseFilter and ApplyFilter methods. Following is the code for  these methods along with the enums and supporting methods.

internal void ApplyFilter(SingleFilterInfo filterParts)
{
    List<T> results;

    // Check to see if the property type we are filtering by implements
    // the IComparable interface.
    Type interfaceType =
        TypeDescriptor.GetProperties(typeof(T))[filterParts.PropName]
        .PropertyType.GetInterface("IComparable");

    if (interfaceType == null)
        throw new InvalidOperationException("Filtered property" +
        " must implement IComparable.");

    results = new List<T>();

    // Check each value and add to the results list.
    foreach (T item in this)
    {
        if (filterParts.PropDesc.GetValue(item) != null)
        {
            IComparable compareValue =
                filterParts.PropDesc.GetValue(item) as IComparable;
            int result =
                compareValue.CompareTo(filterParts.CompareValue);
            if (filterParts.OperatorValue ==
                FilterOperator.EqualTo && result == 0)
                results.Add(item);
            if (filterParts.OperatorValue ==
                FilterOperator.GreaterThan && result > 0)
                results.Add(item);
            if (filterParts.OperatorValue ==
                FilterOperator.LessThan && result < 0)
                results.Add(item);
        }
    }
    this.ClearItems();
    foreach (T itemFound in results)
        this.Add(itemFound);
}

internal SingleFilterInfo ParseFilter(string filterPart)
{
    SingleFilterInfo filterInfo = new SingleFilterInfo();
    filterInfo.OperatorValue = DetermineFilterOperator(filterPart);

    string[] filterStringParts =
        filterPart.Split(new char[] { (char)filterInfo.OperatorValue });

    filterInfo.PropName =
        filterStringParts[0].Replace("[", "").
        Replace("]", "").Replace(" AND ", "").Trim();

    // Get the property descriptor for the filter property name.
    PropertyDescriptor filterPropDesc =
        TypeDescriptor.GetProperties(typeof(T))[filterInfo.PropName];

    // Convert the filter compare value to the property type.
    if (filterPropDesc == null)
        throw new InvalidOperationException("Specified property to " +
            "filter " + filterInfo.PropName +
            " on does not exist on type: " + typeof(T).Name);

    filterInfo.PropDesc = filterPropDesc;

    string comparePartNoQuotes = StripOffQuotes(filterStringParts[1]);
    try
    {
        TypeConverter converter =
            TypeDescriptor.GetConverter(filterPropDesc.PropertyType);
        filterInfo.CompareValue =
            converter.ConvertFromString(comparePartNoQuotes);
    }
    catch (NotSupportedException)
    {
        throw new InvalidOperationException("Specified filter" +
            "value " + comparePartNoQuotes + " can not be converted" +
            "from string. Implement a type converter for " +
            filterPropDesc.PropertyType.ToString());
    }
    return filterInfo;
}

internal FilterOperator DetermineFilterOperator(string filterPart)
{
    // Determine the filter's operator.
    if (Regex.IsMatch(filterPart, "[^>^<]="))
        return FilterOperator.EqualTo;
    else if (Regex.IsMatch(filterPart, "<[^>^=]"))
        return FilterOperator.LessThan;
    else if (Regex.IsMatch(filterPart, "[^<]>[^=]"))
        return FilterOperator.GreaterThan;
    else
        return FilterOperator.None;
}

internal static string StripOffQuotes(string filterPart)
{
    // Strip off quotes in compare value if they are present.
    if (Regex.IsMatch(filterPart, "'.+'"))
    {
        int quote = filterPart.IndexOf('\'');
        filterPart = filterPart.Remove(quote, 1);
        quote = filterPart.LastIndexOf('\'');
        filterPart = filterPart.Remove(quote, 1);
        filterPart = filterPart.Trim();
    }
    return filterPart;
}

      
public struct SingleFilterInfo
{
    internal string PropName;
    internal PropertyDescriptor PropDesc;
    internal Object CompareValue;
    internal FilterOperator OperatorValue;
}

// Enum to hold filter operators. The chars
// are converted to their integer values.
public enum FilterOperator
{
    EqualTo = '=',
    LessThan = '<',
    GreaterThan = '>',
    None = ' '
}

There's a lot in my paper that I am leaving out of this post. Hopefully, I've whet your appetite and you'll download the code and paper. The paper is included as part of the zip file. You can get it all here: Implementing Filtering for Windows Forms Data Binding. Enjoy!

Posted by cherylws | 1 Comments

DataGridView Watermark Cell

I returned from my end-of-the-year vacation to find the following reader comment in my inbox:
> I am trying to create a custom DGV column that will show a watermark for the empty cells , in the same manner like windows live search box ... any advice?

In case you are not familiar with this behavior, a watermark is typically a bit of help text that appears in a text box when it is "empty". For example, the search box in the upper-right corner of IE7 says "Live Search" by default, and the MSDN Library search box says "Search MSDN with Live Search". Typically, the text is in italics or is grey to differentiate it from an ordinary textbox value. As soon as you click in the text box, the help text disappears so that you can type your search terms without distraction.

This seemed like an interesting challenge, so I implemented the DataGridViewWatermarkCell and DataGridViewWatermarkColumn classes shown at the end of this post. All the action is in the cell's Paint method. The main bit of code just sets the formattedValue parameter in the Paint method to the watermark text and tweaks the cell styles.

Of course, the watermark code is only used at run time when there is no cell value, the watermark text has been set, and the cell is not in edit mode. The first part of the if statement in the cell Paint method prevents painting issues in the designer:

if ((OwningColumn.Site == null || !OwningColumn.Site.DesignMode) && ...

It appears that the OwningColumn only has a Site in design mode, but I included the DesignMode check to make the code more readable. The rest of the if statement checks for edit mode (but only if the cell's row is not shared - in which case RowIndex == -1), checks whether the watermark text has been set, and checks whether there is a cell value (using GetValue in case the row is shared).

There are a few other tasks needed to make the code usable in the Visual Studio designer. The column class, for example, is simply a column specialized to use watermark cells by default, and enables you to use the designer to add a watermark column and set the WatermarkText property. Like all specialized DataGridView column types, setting the property at the column level propogates the value to the cell template and to all existing cells in that column. You can override the column value for individual cells by setting the cell WatermarkText property only after you set the column property.

Another important designer consideration is the cell's Clone method override. You must override Clone to copy the WatermarkText value. If you leave out the Clone method, you can change the column WatermarkText value in the designer, but the value won't stick. Note that the column does not have to override Clone because the column always gets its WatermarkText value from the template cell. The cell's Clone method does the work for both the cell and the column.

So there you have it! Clearly, this is a primitive implementation, but it should give you the basic idea if you want to take it further. For example, you might want to make more of the watermark properties (such as styles) available to the client programmer, or allow an Image watermark.

Enjoy!

using System;

using System.ComponentModel;

using System.Drawing;

using System.Windows.Forms;

 

namespace DataGridViewWatermark

{

    public class DataGridViewWatermarkColumn : DataGridViewTextBoxColumn

    {

        public DataGridViewWatermarkColumn()

        {

            CellTemplate = new DataGridViewWatermarkCell();

        }

 

        public String WatermarkText

        {

            get

            {

                if (((DataGridViewWatermarkCell)CellTemplate) == null)

                {

                    throw new InvalidOperationException("cell template required");

                }

                return ((DataGridViewWatermarkCell)CellTemplate).WatermarkText;

            }

            set

            {

                if (this.WatermarkText != value)

                {

                    ((DataGridViewWatermarkCell)CellTemplate).WatermarkText = value;

                    if (this.DataGridView != null)

                    {

                        DataGridViewRowCollection dataGridViewRows =

                            this.DataGridView.Rows;

                        int rowCount = dataGridViewRows.Count;

                        for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)

                        {

                            DataGridViewRow dataGridViewRow =

                                dataGridViewRows.SharedRow(rowIndex);

                            DataGridViewWatermarkCell cell =

                                dataGridViewRow.Cells[this.Index]

                                as DataGridViewWatermarkCell;

                            if (cell != null)

                            {

                                cell.WatermarkText = value;

                            }

                        }

                    }

                }

            }

        }

 

    }

 

    public class DataGridViewWatermarkCell : DataGridViewTextBoxCell

    {

        private String watermarkTextValue;

 

        public String WatermarkText

        {

            get { return watermarkTextValue; }

            set { watermarkTextValue = value; }

        }

 

        public override object Clone()

        {

            DataGridViewWatermarkCell cell = (DataGridViewWatermarkCell)base.Clone();

            cell.WatermarkText = this.WatermarkText;

            return cell;

        }

 

        protected override void Paint(Graphics graphics, Rectangle clipBounds,

            Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState,

            object value, object formattedValue, string errorText,

            DataGridViewCellStyle cellStyle,

            DataGridViewAdvancedBorderStyle advancedBorderStyle,

            DataGridViewPaintParts paintParts)

        {
            if ((OwningColumn.Site == null || !OwningColumn.Site.DesignMode) &&
                (RowIndex < 0 || !IsInEditMode) && !
String.IsNullOrEmpty(WatermarkText) &&
                (GetValue(rowIndex) ==
null || GetValue(rowIndex) == DBNull.Value))

            {

                cellStyle.Font = new Font(cellStyle.Font, FontStyle.Italic);

                cellStyle.ForeColor = Color.Gray;

                formattedValue = WatermarkText;

            }

            base.Paint(graphics, clipBounds, cellBounds, rowIndex,

                cellState, value, formattedValue, errorText,

                cellStyle, advancedBorderStyle, paintParts);

        }

    }

}

 

 

Posted by Karl Erickson | 2 Comments
Filed under:

Client Application Services and WPF

Although my sample for Client Application Services (CAS) uses Windows Forms, you can adapt it to Windows Presentation Foundation (WPF) with little effort. The configuration steps are the same, and the client source code is the same, although some of the code would go in WPF equivalents of Windows Forms event handlers (for example, using an App.Startup or Window.Loaded event handler rather than a Form.Load event handler).

However, there is a peculiarity in WPF that prevents things from just working without a small adjustment. After authentication using CAS, the static Thread.CurrentPrincipal property gets set to an instance of ClientRolePrincipal. Internally, CAS checks for this value to determine whether a user is still logged in. Unfortunately, in WPF, Thread.CurrentPrincipal reverts to the default value (a GenericPrincipal instance) some time after the Window.Loaded event.

For details, see this forum post. The fix for CAS is simple, however: just add the following line of code after your Membership.ValidateUser call in your App.Startup or Window.Loaded event handler:

AppDomain.CurrentDomain.SetThreadPrincipal(Thread.CurrentPrincipal);

At this point, Thread.CurrentPrincipal is still set to the ClientRolePrincipal object. The SetThreadPrincipal method call is what makes it stay there.

Implementing the IBindingListView for filtering

Due to customer feedback and requests, I've been working on an article that demonstrates a simple implementation of the filtering portion of the IBindingListView. This implementation works with the DataGridViewAutoFilter code that Karl first published in the summer of 2006 and allows you to search, sort and filter a list of business objects. In the course of writing the paper, I realized the filtering implementation could be even simpler than what I had for my article and still correctly interact with Karl's code.

For the simple filtering, I start with my generic implementation of searching and sorting demonstrated in my article published in summer 2006. I then use the FindCore method to find matches for the filter value. The code listing is lengthy so I've posted the complete listing here.

That said; I want to walk you through some of the code. The filtering expression my code handles follows a subset of the guidelines established by the DataColumn.Expression property. I'll look for a property name followed by an equals followed by the filter value, typically in quotes. For example, LastName="Smith". In this simple example, I am only concerned with values equal to the filter value. My upcoming article will cover more complex filtering.

The IBindingListView interface contains three members related to filter; the SupportsFiltering and Filter properties as well as the RemoveFilter method. The first step is to implement the SupportsFiltering property to always return true.

public bool SupportsFiltering

{

    get { return true; }

}

Implementing the Filter property is more difficult and requires some preliminary steps. When the filter value is set, the filter is applied to the list and the list returns the items that meets the filter criteria. In addition, the original list must be cached because the filter can be removed, in which case, the list should revert to its original contents. Therefore, the unfiltered list is stored in a read-only property named UnfilteredList. I chose a read-only property to allow for the code to be unit-tested. The following code shows the declaration for the unfiltered list.

List<T> unfilteredListValue = new List<T>();

public List<T> UnfilteredList

{

    get { return unfilteredListValue; }

}

Next is the Filter property itself.

private string filterValue = null;

public string Filter

 {

    ...

 }

 The getter for the filter property is straightforward.

get

{

     return filterValue;

}

However, the setter is more complicated and requires some explanantion. The first step in setting the Filter property is to check whether the filter value has changed. If the filter value is unchanged, the set operation should not do anything. If the Filter value has changed, I temporarily disable change notifications for the list. It is important that I do this step because I will be manipulating the contents of the list. In the context of an application, bound controls should not be notified of these internal changes. To disable change notifications, I set the RaiseListChangedEvents property to false; This property is set back to true when the set call is finished. Next, I'll check for a filter value of null. Null indicates the filter has been removed and the original list contents should be reset. I must also check the filter value to see whether it matches the expected format, which I do with a regular expression. If the filter value does not match the expected format, or the filter expression indicates multi-column filtering, an ArgumentException is thrown. If the filter complies with the expected format, I parse the filter string and apply it to the data source. Finally, I set the RaiseListChangedEvents property back to true and call raise a ListChanged event to signal bound controls to refresh. In the following code, the filter parsing and application is accomplished by two separate methods; GetFilterParts and ApplyFilter, which I'll discuss next.

set

{

    if (filterValue != value)

    {

        RaiseListChangedEvents = false;

        // If filter value is null, reset list.

        if (value == null)

        {

            this.ClearItems();

            foreach (T t in unfilteredListValue)

                this.Items.Add(t);

            filterValue = value;

        }

 

         // If the value is empty string, do nothing.

         // This behavior is compatible with DataGridView

         // AutoFilter code.

        else if (value == "") { }

         // If the value is not null or string, than process

         // normal.

        else if (Regex.Matches(value,

         "[?[\\w ]+]? ?[=] ?'?[\\w|/: ]+'?",

         RegexOptions.Singleline).Count == 1)

        {

            // If the filter is not set.

            unfilteredListValue.Clear();

            unfilteredListValue.AddRange(this.Items);

            filterValue = value;

            GetFilterParts();

            ApplyFilter();

        }

        else if (Regex.Matches(value,

            "[?[\\w ]+]? ?[=] ?'?[\\w|/: ]+'?",

            RegexOptions.Singleline).Count > 1)

            throw new ArgumentException("Multi-column" +

              "filtering is not implemented.");

 

        else throw new ArgumentException("Filter is not" +

               "in the format: propName = 'value'.");

 

        RaiseListChangedEvents = true;

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

    }

}

In this example, parsing the filter is straightforward. I look for the "=" and grab the property name and filter value. The filter value is converted to the same type as the specified property. If the property is not found, or the conversion cannot be performed, an exception is thrown.

First, I expose some properties and private fields to store the filter parts.

private string filterPropertyNameValue;

private Object filterCompareValue;

public string FilterPropertyName

{

    get { return filterPropertyNameValue; }

}

public Object FilterCompare

{

    get { return filterCompareValue; }

Then I get the parts.

public void GetFilterParts()

{

    string[] filterParts = Filter.Split(new char[] { '=' },

        StringSplitOptions.RemoveEmptyEntries);

    filterPropertyNameValue =

        filterParts[0].Replace("[", "").Replace("]", "").Trim();

    PropertyDescriptor propDesc =

    TypeDescriptor.GetProperties(typeof(T))[filterPropertyNameValue.ToString()];

    if (propDesc != null)

    {

        try

        {

            TypeConverter converter = TypeDescriptor.GetConverter(propDesc.PropertyType);

            filterCompareValue = converter.ConvertFromString(filterParts[1].Replace("'", "").Trim());

        }

        catch (NotSupportedException)

        {

            throw new ArgumentException("Specified filter value " +

              FilterCompare + " can not be converted from string." +

              "..Implement a type converter for " + propDesc.PropertyType.ToString());

        }

    }

    else throw new ArgumentException("Specified property '" +

       FilterPropertyName + "' is not found on type " +

       typeof(T).Name + ".");

}

Following is the ApplyFilter method.  This method is pretty simple; I loop through the items using the FindCore method to get all of the matches to the filter value.

private void ApplyFilter()

{

    unfilteredListValue.Clear();

    unfilteredListValue.AddRange(this.Items);

    List<T> results = new List<T>();

 

PropertyDescriptor propDesc =

  TypeDescriptor.GetProperties(typeof(T))[FilterPropertyName];

    if (propDesc != null)

    {

        int tempResults = -1;

        do

        {

            tempResults = FindCore(tempResults + 1, propDesc, FilterCompare);

            if (tempResults != -1)

                results.Add(this[tempResults]);

        } while (tempResults != -1);

    }

    this.ClearItems();

    if (results != null && results.Count > 0)

    {

        foreach (T itemFound in results)

            this.Add(itemFound);

    }

}

The final step is to implement RemoveFilter. This is simple; set Filter to null.

public void RemoveFilter()

{

    if (Filter != null) Filter = null;

}

Hopefully this helps you get started with filtering and please look for my upcoming article that shows a more complex filtering implementation.

Posted by cherylws | 2 Comments

DataGridviewAutoFilter sample updated

I've made a few bug-fixes to my DataGridViewAutoFilter sample, which is described in Building a Drop-Down Filter List for a DataGridView Column Header Cell. It might be a week or two until the article is updated (as of December 5, 2007), but the new sample download has already been posted. The download contains C# and VB projects, plus the updated article in Word 2003 format.

Here's a summary of the fixes:

  • The code that populates the drop-down list now does a case-insensitive match between the column's DataPropertyName and the data source properties. This is necessary because data-source property-access APIs are case-sensitive but DataPropertyName is case-insensitive. This caused problems with columns that are manually bound using a different casing than is used in the data source.
  • The code that populates the drop-down now does a null-value check before resetting the filter. The previous version caused some issues when binding to an lBindingListView implementation that my colleague Cheryl is working on for a forthcoming whitepaper.
  • The drop-down button size is now constrained by the header cell width as well as the height. Previously, the drop-down button for a very narrow column would overlap the adjacent column.
  • The drop-down button painting code is no longer executed if the button is of zero size. Previously, this caused an exception, for example when using the mouse to reduce the height of the column headers row to zero.

Client Application Services sample updated for Visual Studio 2008 RTM

I have updated my Client Application Services sample (previous posts here and here) to work with the Visual Studio 2008 RTM release. See UsingClientApplicationServices.zip.

Additionally, the final documentation is now available on MSDN. See:

If you have used a previous version of the sample, you might need to deal with its remnants if you run into problems with this new version. Here are a few things you can try:

  • If you get a CookieException when accessing the remote services, try clearing your IE cookie cache.
  • If your application does not seem to be accessing the remote services, but everything looks properly configured, try deleting the application data folder. This folder location is indicated by the static Application.UserAppDataPath property - the default location for Vista is C:\Users\<username>\AppData\Roaming\MSIT.

Client Application Services sample and walkthrough updated

Back in May, I posted an early version of my Client Application Services walkthrough, which was used as a lab session at TechEd. Since then, there have been many improvements to both Visual Studio and the walkthrough. For one thing, the Beta 2 version of Orcas (now officially known as "Visual Studio 2008") was released. Unfortunately, this meant that the original Client Application Services sample became obsolete, since it won't run on Beta 2 without updating the project config files.

I have now replaced the original UsingClientApplicationServices.zip file, so if you'd like to preview the RTM version of this walkthrough and run the sample on the Beta 2 release, you can download the new UsingClientApplicationServices.zip. Updates include:

  • A "remember me" feature for the login dialog box.
  • A logout button.
  • More robust code throughout, demonstrating extensive exception handling for fallback scenarios such as login expiration and server unavailability.

Enjoy!

UPDATE (12/04/2007): UsingClientApplicationServices.zip now includes VB and C# projects that will work with the Visual Studio 2008 RTM release. Additionally, the full documentation is now available on MSDN. See:

 

Tech-Ed Notes

Thanks to everyone who stopped by the Acropolis and Windows Forms booth at Tech-Ed! Double thanks if you answered a few of my questions about documentation. You can be sure that the Windows .NET Client UE team will be reading your comments.

A few of you reported your frustration with the Smart Client Orcas Features lab, as you were not able to cut and paste from the lab document to Visual Studio. In addition to viewing and downloading the lab from the Tech-Ed site, you can check it out right on this blog, as Karl posted the walkthrough a couple of weeks ago.

If you didn't have the chance to attend Tech-Ed, or did attend, but want to revisit your favorite parts, make sure and check out Virtual Tech-Ed.

Posted by cherylws | 0 Comments

Client Application Services in Windows Forms: End-to-End Walkthrough Available

The Beta 1 release of Visual Studio Code Name "Orcas" includes a new feature called Client Application Services. This feature enables Windows client applications to easily access user-management services hosted by a centralized ASP.NET Web service application.

The documentation for client application services is still in progress, but we have created an end-to-end walkthrough to demonstrate the key features. This walkthrough will be used as a lab session at the upcoming TechEd 2007 conference, but if you want a sneak peek, download the attached zip file, which contains the walkthrough and sample projects in Visual Basic and C#.

(From the walkthrough introduction:) In this walkthrough, you perform the following tasks:

  • Create a Windows Forms application and use the Visual Studio project designer to enable and configure client application services. 
  • Create a simple ASP.NET Web Service application to host the application services and test your client configuration.
  • Add forms authentication to your application. You will start by using a hard-coded user name and password to test the service. You will then add a login form by specifying it as a credentials provider in your application configuration. 
  • Add role-based functionality, enabling and displaying a button only for users in the "manager" role. 
  • Access Web settings. You will start by loading Web settings for an authenticated (test) user on the Settings page of the project designer. You will then use the Windows Forms Designer to bind a text box to a Web setting. Finally, you will save the modified value back to the server. 
  • Enable offline mode. You will provide a check box so that users can specify their connection status. You will then use this value to specify whether the client application service providers will use locally cached data instead of accessing their Web services. Finally, you will re-authenticate the current user when the application returns to online mode.

UPDATE (9/27/2007): I have extensively revised and updated this walkthrough for the Visual Studio 2008 RTM documentation. To preview these updates, download the attached UsingClientApplicationServices.zip file, which includes the new walkthrough plus VB and C# projects containing updated Visual Studio solutions that work with the Visual Studio 2008 (formerly code-named "Orcas") Beta 2 release. These updates include a "remember me" login feature, a logout button, and more robust code that demonstrates appropriate exception handling for cases such as login expiration and server unavailability. Enjoy!

UPDATE (12/04/2007): The attached UsingClientApplicationServices.zip now includes VB and C# projects that will work with the Visual Studio 2008 RTM release. Additionally, the full documentation is now available on MSDN. See:

What does "not supported" mean?

Periodically someone questions me about a caution that appears in all of the System.Drawing.* namespace overview topics. It reads:

"Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions."

This warning has generated a lot of speculation and confusion and I apologize for this. This caution confused me when I was asked to add it to the documentation. I'd seen examples demonstrating how to use System.Drawing classes in ASP.NET pages and I questioned whether the caution should be added. The explanation I was given is that the System.Drawing.* classes were designed for use with Windows Forms and were tested for use with Windows Forms. When we say they are not supported in services or ASP.NET applications (also a service), we mean exactly that; they are not supported. We don't mean they won't work in an ASP.NET application or that we are trying to cover up some known bug that occurs when you use them in a service. The bottom line is that if you call Microsoft Product Support Services regarding a problem you have using a System.Drawing.* class in a service, they will not offer free support.

 Hopefully this clears up any confusion.

Posted by cherylws | 1 Comments

Windows Forms and WPF Interop Docs

Now that interest in WPF is really heating up, we've been getting more inquiries about WinForms and WPF interop. Fortunately, we have a lot of coverage in the SDK docs, which shipped in November:

Migration and Interoperability

 

  • How to: Enable Visual Styles in a Hybrid Application
  • How to: Host a Windows Presentation Foundation Control in Windows Forms by Using ElementHost
  • Layout Considerations for the WindowsFormsHost Element
  • Supported Scenarios in Windows Presentation Foundation and Windows Forms Interoperation
  • Troubleshooting Hybrid Applications
  • Walkthrough: Arranging Windows Forms Controls in Windows Presentation Foundation
  • Walkthrough: Binding to Data in Hybrid Applications
  • Walkthrough: Hosting a Windows Forms Composite Control in Windows Presentation Foundation
  • Walkthrough: Hosting a Windows Forms Control in Windows Presentation Foundation
  • Walkthrough: Hosting a Windows Forms Control in Windows Presentation Foundation by Using XAML
  • Walkthrough: Hosting a Windows Presentation Foundation Composite Control in Windows Forms
  • Walkthrough: Hosting a Windows Presentation Foundation Control in Windows Forms
  • Walkthrough: Hosting an ActiveX Control in Windows Presentation Foundation
  • Walkthrough: Hosting an ActiveX Control in Windows Presentation Foundation by Using XAML
  • Walkthrough: Localizing a Hybrid Application
  • Walkthrough: Manually Adding a Windows Presentation Foundation Element to a Windows Forms Project
  • Walkthrough: Manually Creating a Windows Presentation Foundation Project Using Visual Studio
  • Walkthrough: Mapping Properties Using the ElementHost Control
  • Walkthrough: Mapping Properties Using the WindowsFormsHost Element
  • Windows Forms and WPF Interoperability Input Architecture
  • Windows Forms and WPF Property Mapping
  • Windows Forms Controls and Equivalent WPF Controls

Also, we have lots of sample code:

  • Arranging Windows Forms Controls in Windows Presentation Foundation Sample
  • Data Binding in Hybrid Applications Sample
  • Hosting a Windows Forms Composite Control in Windows Presentation Foundation Sample
  • Manually Creating a Windows Presentation Foundation Project Sample
  • Hosting a Windows Presentation Foundation Composite Control in Windows Forms Sample
  • Localizing a Hybrid Application Sample
  • Hosting a Windows Forms Control in Windows Presentation Foundation Sample
  • Enabling Visual Styles in a Hybrid Application Sample
  • Hosting a Windows Forms Control in Windows Presentation Foundation by Using XAML Sample
  • Hosting a Simple Windows Presentation Foundation Control in Windows Forms Sample
  • Hosting an ActiveX Control in Windows Presentation Foundation by Using XAML Sample
  • Hosting an ActiveX Control in Windows Presentation Foundation Sample
  • Mapping Properties Using the WindowsFormsHost Element Sample
  • Mapping Properties Using the ElementHost Control Sample

We have some designer-oriented topics as well, which shipped in Orcas Beta 1. These haven't been published at MSDN yet: 

  • How to: Copy and Paste an ElementHost Control at Design Time
  • Using Windows Presentation Foundation Controls
  • Walkthrough: Arranging Windows Presentation Foundation Content on Windows Forms at Design Time
  • Walkthrough: Assigning Windows Presentation Foundation Content on Windows Forms at Design Time
  • Walkthrough: Changing Properties of a Hosted Windows Presentation Foundation Element at Design Time
  • Walkthrough: Copying and Pasting an ElementHost Control into Separate Windows Forms
  • Walkthrough: Creating New Windows Presentation Foundation Content on Windows Forms at Design Time
  • Walkthrough: Styling Windows Presentation Foundation Content

Hope you can use them to build some neat hybrid apps.

 

DataGridView Drop-Down Filtering Tip

Recently, a reader asked how to detect filter changes with the code in Building a Drop-Down Filter List for a DataGridView Column Header Cell. That article describes how to derive from DataGridViewColumnHeaderCell, so it seems like a trivial matter to add a FilterChanged event that occurs whenever the user changes a value. However, this solution has a number of drawbacks, the primary one being that it doesn't work. The DataGridView control uses cloning in many of its internal operations, so the cell that the user interacts with is not the cell that you've attached an event handler to.

Besides, why should you attach handlers to all those cells when the changes affects the entire DataGridView? The example in the article uses a DataGridView.DataBindingComplete event handler in the Form code to update the filter status label (the label that says things like "13 of 42 records found"). This could work, but the DataBindingComplete event occurs whenever the data source changes in any way and the grid needs updating. This is no problem for a filter status label that automatically ignores irrelevant changes, but a filter changed event needs more precision, and you would have to add code to ignore things like sorting.

The BindingSource class does not have a FilterChanged event, but fortunately, its Filter property is virtual, so we can add a FilterChanged event to a derived class, as shown in the following code. Note that the code checks the RaiseListChangedEvents property before raising the event; this is necessary to avoid problems when the filter is temporarily changed in order to calculate values like the status text.

        class BindingSourceWithFilterChanged : BindingSource

        {

            public BindingSourceWithFilterChanged(object dataSource, string dataMember) :

                base(dataSource, dataMember) { }

 

            public event EventHandler FilterChanged;

 

            protected virtual void OnFilterChanged(EventArgs e)

            {

                if (FilterChanged != null && RaiseListChangedEvents)

                {

                    FilterChanged(this, e);

                }

            }

 

            public override string Filter

            {

                get

                {

                    return base.Filter;

                }

                set

                {

                    OnFilterChanged(EventArgs.Empty);

                    base.Filter = value;

                }

            }

        }

So the solution is to replace the BindingSource object in your Form code with a BindingSourceWithFilterChanged object, and then handle its FilterChanged event. (And in case event ordering is import for your project, the FilterChanged event occurs before the DataGridView.RowsAdded and DataBindingComplete events that also occur when the user changes a filter value.) 

It's a juggling act

If you are reading this blog post, it’s likely you saw Karl’s post highlighting some of the recent updates and improvements we’ve made to the existing Windows Forms documentation set. You might be wondering how we decided to make this particular set of changes and why we didn’t make more improvements. The following is a little insight into the job of a Programming Writer here at Microsoft and how we make these kinds of decisions.

 

First, we (the Programming Writers) have to figure out exactly what documentation improvements to make. We gather feedback on topics in a number of ways:

  • MSDN ratings and comments generated from the “Click to Rate and Give Feedback” tool at the top of each online topic
  • Bugs and suggestions filed on http://connect.microsoft.com/.
  • E-mail generated when you click the “Send comments about this topic” link at the bottom of topics that are installed on your local machine.

You’ll notice that we make decisions about which topics to change and improve almost exclusively based on feedback from our customers.

 

When making improvements to the documentation, we want to fix the most egregious errors first. For less serious changes, we want to target topics that are the most popular. Some questions we ask are:

  • How many customers are impacted by this issue?
  • What is the scope of the error? For example, is it technically inaccurate, or just not as descriptive as it could be?
  • How long will it take to fix?

 A Programming Writer typically owns a large chunk of legacy content as well as a documentation set for the next product release. This means that not only are we multifaceted programmers and writers, we also must be skilled at managing multiple projects and deadlines. I say multifaceted, because before our team recently hired a couple of new folks; I owned legacy topics around data binding, graphics, visual styles, user input, various Windows Forms controls, and the list goes on. It’s up to me to be an expert in all of those content areas, whether or not I wrote the content originally, so that I can intelligently respond to customer feedback and make decisions about updating the topics related to these features.  In addition—and this is where the project management comes in—I am also typically ramping up or writing content for my new features for the next release of the product. Don’t get me wrong; I am not complaining. I think I can speak for most Programming Writers when I say I love the variety and continuous challenges of my position.

 

Hopefully this explains why you might not get as many documentation updates as often as you would like. As Karl mentions, to make sure you are getting the latest updates, you should always check the online version of the documentation. Also, there are a couple of other things you can do. If you have a problem with your code, make sure and use the MSDN forums as a means to get your questions answered. These forums are monitored by experts inside and outside of Microsoft and they have a lot of knowledge to spread around. Secondly, and on the topic of spreading knowledge, if you have a better code example than the official documentation, you can provide some insightful tips about a particular API, or have other improvements to the existing content, make sure and check out the Community Content section at the bottom of each online document. This section allows you, the customer, to suggest changes and additions in a wiki-like format.

 

Help us make the best changes by continuing to provide ratings and comments on the documentation in any of the ways I mentioned previously. Tell us specifically what can be improved and why, and we’ll do our best to act on it.

 

Posted by cherylws | 1 Comments

Recent Windows Forms Documentation Updates

Since Visual Studio 2005 was released, we've received a fair amount of feedback on the documentation. We have been able to clarify and update many help topics in response to this feedback, so please keep it coming! You can provide feedback from links directly on the topics, through Microsoft Connect, or through this blog.  

To view updated documentation within the integrated Visual Studio help viewer, make sure that your Help options are set to load online content. You can do this through the Tools menu in Visual Studio by selecting Options, then navigating to the Environment | Help | Online section of the Options dialog box.

You can also view updated topics on MSDN2 at the URLs below. Each MSDN2 topic indicates which version of the product that topic is for. To ensure you are looking at the latest version of the topic, you will want to select the versions for .NET Framework 3.0 or Visual Studio Codename Orcas.

The following list shows conceptual topics that we've updated in the past several months. We've also updated reference topics as indicated on the Windows Forms Reference Updates page.

Title Link
Accessing Unexposed Members on the Managed HTML Document Object Model http://msdn2.microsoft.com/en-us/library/ms171716(vs.90).aspx
Code for Windows Forms Applications http://msdn2.microsoft.com/en-us/library/xxwd7aah.aspx
Creating a New Windows Form http://msdn2.microsoft.com/en-us/library/ms229599.aspx
Data Binding and Windows Forms http://msdn2.microsoft.com/en-us/library/c8aebh9k.aspx
Extending Design-Time Support http://msdn2.microsoft.com/en-us/library/37899azc(vs.90).aspx
How to: Add and Remove Nodes with the Windows Forms TreeView Control http://msdn2.microsoft.com/en-us/library/70w4awc4(vs.90).aspx
How to: Bind a Windows Forms Control to a Type http://msdn2.microsoft.com/en-us/library/cw8f1c66(vs.90).aspx
How to: Determine Checked Items in the Windows Forms CheckedListBox Control http://msdn2.microsoft.com/en-us/library/e954th47(vs.90).aspx
How to: Develop a Simple Windows Forms Control http://msdn2.microsoft.com/en-us/library/649xahhe(vs.90).aspx
How to: Disable URL Activation of ClickOnce Applications Using the Designer http://msdn2.microsoft.com/en-us/library/ms404230.aspx
How to: Display Option Buttons in a MenuStrip (Windows Forms) http://msdn2.microsoft.com/en-us/library/ms404318(vs.90).aspx
How to: Display Print Preview in Windows Forms Applications http://msdn2.microsoft.com/en-us/library/274tyhz7(vs.90).aspx
How to: Enable Windows XP Visual Styles http://msdn2.microsoft.com/en-us/library/5d1acks5(vs.90).aspx
How to: Give Your Control a Transparent Background http://msdn2.microsoft.com/en-us/library/wk5b13s4(vs.90).aspx
How to: Make a Startup Windows Form Invisible http://msdn2.microsoft.com/en-us/library/754w18dd(vs.90).aspx
How to: Move Through a DataSet with the Windows Forms BindingNavigator Control http://msdn2.microsoft.com/en-us/library/s4b01sz7(vs.90).aspx
How to: Play a Sound Embedded in a Resource from a Windows Form http://msdn2.microsoft.com/en-us/library/3w5b27z4(vs.90).aspx
How to: Provide a Toolbox Bitmap for a Control http://msdn2.microsoft.com/en-us/library/4wk1wc0a(vs.90).aspx
How to: Select an Item in the Windows Forms ListView Control http://msdn2.microsoft.com/en-us/y4x56c0b.aspx
How to: Simulate Mouse and Keyboard Events in Code http://msdn2.microsoft.com/en-us/ms171548.aspx
MenuStrip Control (Windows Forms) http://msdn2.microsoft.com/en-us/library/ms171649.aspx
PrintDocument Component Overview (Windows Forms) http://msdn2.microsoft.com/en-us/library/679kbhe3(vs.90).aspx
Server and Client Configuration Issues in ClickOnce Deployments http://msdn2.microsoft.com/en-us/library/ms228998.aspx
Troubleshooting Control and Component Authoring http://msdn2.microsoft.com/en-us/library/wz0k6226(vs.90).aspx
Troubleshooting Design-Time Development http://msdn2.microsoft.com/en-us/library/ms171843(vs.90).aspx
Walkthrough: Authoring a Composite Control with Visual Basic http://msdn2.microsoft.com/en-us/library/c316f119(vs.90).aspx
Walkthrough: Automatically Populating the Toolbox with Custom Components http://msdn2.microsoft.com/en-us/library/fw694kde(vs.90).aspx
Walkthrough: Creating a Windows Forms Control That Takes Advantage of Visual Studio Design-Time Features http://msdn2.microsoft.com/en-us/library/35ea88wb(vs.90).aspx
Walkthrough: Retrieving Dialog Box Information Collectively Using Objects http://msdn2.microsoft.com/en-us/library/cakx2hdw(vs.90).aspx
Walkthrough: Running an Operation in the Background http://msdn2.microsoft.com/en-us/library/ms233672(vs.90).aspx
Windows Forms Designer Error Messages http://msdn2.microsoft.com/en-us/library/ms233640(vs.90).aspx

 

Maintaining position in a child table

I've recently written a document covering some changes to the CurrencyManager in .NET Framework version 2.0. I'll summarize the document here, as it will not be published for a few months, and this subtle change in behavior could be causing problems in .NET Framework 1.1 applications.

Starting with the .NET Framework version 2.0, when you display data in a parent/child view you might have to take extra steps to make sure that the currently selected row in the child table is not reset to the first row of the table. In order to do this, you will have to cache the child table position and reset it after the parent table changes. Typically the child reset occurs the first time a field in a row of the parent table changes. Following are the steps to cache and reset the child position:

First cache the child position:

1.      Declare an integer variable to store the child list position and a Boolean variable to store whether to cache the child position.

private int cachedPosition = -1;

private bool cacheChildPosition = true;

 

2.      Handle the CurrencyManager.ListChanged event for the child binding's CurrencyManager and check for a ListChangedType of ListChangedType.Reset.

 

3.      Check the current position of the CurrencyManager. If it is greater than first entry in the list (typically 0), save it to a variable.

void relatedCM_ListChanged(object sender, ListChangedEventArgs e)

{

    // Check to see if this is a caching situation.

    if (cacheChildPosition)

    {

        // If so, check to see if it is a reset situation,

        // and the current position is greater than zero.

        CurrencyManager relatedCM = sender as CurrencyManager;

        if (e.ListChangedType == ListChangedType.Reset &&

           relatedCM.Position > 0)

 

            // If so, cache the position of the child table.

            cachedPosition = relatedCM.Position;

    }

}

 

4.      Handle the parent list's CurrentChanged event for the parent currency manager. In the handler, set the Boolean value to indicate it is not a caching scenario. If the CurrentChanged occurs, the change to the parent is a list position change and not an item value change.

void bindingSource1_CurrentChanged(object sender, EventArgs e)

{

    // If the CurrentChanged event occurs, this is not a caching

    // situation.

    cacheChildPosition = false;

}

 

Then reset the child position, if necessary:

 

1.     Handle the PositionChanged event for the child binding's CurrencyManager.

2.      Reset the child table position to the cached position saved in the previous procedure.

void relatedCM_PositionChanged(object sender, EventArgs e)

{

    // Check to see if this is a caching situation.

    if (cacheChildPosition)

    {

        CurrencyManager relatedCM = sender as CurrencyManager;

 

        // If so, check to see if the current position is

        // not equal to the cached position and the cached

        // position is not out of bounds.

        if (relatedCM.Position != cachedPosition && cachedPosition

            > 0 && cachedPosition < relatedCM.Count)

        {

            relatedCM.Position = cachedPosition;

            cachedPosition = -1;

        }

    }

}

 

 

Following is the code for a sample application that demonstrates the reset behavior and the cache and reset of the child position.

To run this example in Visual Studio, you'll need to paste it into a blank project, or you can compile it on the command line.

When you run the sample, click the Clear parent field button to cause a change in a field of the parent table. Notice that the selected row in the child table does not change. Then, close and run the example again. (You need to do this because the reset behavior occurs only on the first change in a parent row.)  Clear the Cache and reset position check box and click the Clear parent field button. Notice that the selected row in the child table changes to the first row.

 

using System;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

 

namespace BT2

{

    public class Form1 : Form

    {

        public Form1()

        {

            InitializeControlsAndDataSource();

        }

 

        // Declare the controls to be used.

        private BindingSource bindingSource1;

        private DataGridView dataGridView1;

        private Button button1;

        private DataGridView dataGridView2;

        private CheckBox cachePositionCheckBox;

        public DataSet set1;

      

        private void InitializeControlsAndDataSource()

        {

            // Initialize the controls and set location, size and

            // other basic properties.

            this.dataGridView1 = new DataGridView();

            this.bindingSource1 = new BindingSource();

            this.button1 = new Button();

            this.dataGridView2 = new DataGridView();

            this.cachePositionCheckBox =

               new System.Windows.Forms.CheckBox();

            this.dataGridView1.ColumnHeadersHeightSizeMode =

                DataGridViewColumnHeadersHeightSizeMode.AutoSize;

            this.dataGridView1.Dock = DockStyle.Top;

            this.dataGridView1.Location = new Point(0, 20);

            this.dataGridView1.Size = new Size(292, 170);

            this.button1.Location =

               new System.Drawing.Point(18, 175);

            this.button1.Size = new System.Drawing.Size(125, 23);

       

            button1.Text = "Clear Parent Field";

            this.button1.Click +=

              new System.EventHandler(this.button1_Click);

            this.dataGridView2.ColumnHeadersHeightSizeMode =

                DataGridViewColumnHeadersHeightSizeMode.AutoSize;

            this.dataGridView2.Location =

               new System.Drawing.Point(0, 225);

            this.dataGridView2.Size =

               new System.Drawing.Size(309, 130);

            this.cachePositionCheckBox.AutoSize = true;

            this.cachePositionCheckBox.Checked = true;

            this.cachePositionCheckBox.Location =

              new System.Drawing.Point(150, 175);

            this.cachePositionCheckBox.Name = "radioButton1";

            this.cachePositionCheckBox.Size =

              new System.Drawing.Size(151, 17);

            this.cachePositionCheckBox.Text =

              "Cache and restore position";

            this.ClientSize = new System.Drawing.Size(325, 420);

            this.Controls.Add(this.dataGridView1);

            this.Controls.Add(this.cachePositionCheckBox);

            this.Controls.Add(this.dataGridView2);

            this.Controls.Add(this.button1);

         

            // Initialize the data.

            set1 = InitializeDataSet();

          

            // Set the data source to the DataSet.

            bindingSource1.DataSource = set1;

          

            //Set the DataMember to the Menu table.

            bindingSource1.DataMember = "Customers";

 

            // Add the control data bindings.

            dataGridView1.DataSource = bindingSource1;

 

            // Set the data source and member for the second

            // DataGridView.

            dataGridView2.DataSource = bindingSource1;

            dataGridView2.DataMember = "custOrders";

 

            // Get the currency manager for the customer orders

            // binding.

            CurrencyManager relatedCM =

              bindingSource1.GetRelatedCurrencyManager(

               "custOrders");

           

            // Set the position in the child table for demonstration

            // purposes.

            relatedCM.Position = 3;

 

            // Handle the current changed event. This event occurs

            // when the current item is changed, but not when a

           // field of the current item is changed.

            bindingSource1.CurrentChanged +=

                new EventHandler(bindingSource1_CurrentChanged);

           

            // Handle the two events for caching and resetting the

            // position.

            relatedCM.ListChanged += new

              ListChangedEventHandler(relatedCM_ListChanged);

            relatedCM.PositionChanged

                += new EventHandler(relatedCM_PositionChanged);

        }

 

      

        // Establish the data set with two tables and a relationship

        // between them.

        private DataSet InitializeDataSet()

        {

            set1 = new DataSet();

            // Declare the DataSet and add a table and column.

            set1.Tables.Add("Customers");

            set1.Tables[0].Columns.Add("CustomerID");

            set1.Tables[0].Columns.Add("Customer Name");

            set1.Tables[0].Columns.Add("Contact Name");

 

            // Add some rows to the table.

            set1.Tables["Customers"].Rows.Add("c1",

             "Fabrikam, Inc.", "Ellen Adams");

            set1.Tables[0].Rows.Add("c2", "Lucerne Publishing",

              "Don Hall");

            set1.Tables[0].Rows.Add("c3", "Northwind Traders",

              "Lori Penor");

            set1.Tables[0].Rows.Add("c4", "Tailspin Toys",

              "Michael Patten");

            set1.Tables[0].Rows.Add("c5", "Woodgrove Bank",

               "Jyothi Pai");

 

            // Declare the DataSet and add a table and column.

            set1.Tables.Add("Orders");

            set1.Tables[1].Columns.Add("CustomerID");

            set1.Tables[1].Columns.Add("OrderNo");

            set1.Tables[1].Columns.Add("OrderDate");

 

            // Add some rows to the table.

            set1.Tables[1].Rows.Add("c1", "119", "10/04/2006");

            set1.Tables[1].Rows.Add("c1", "149", "10/10/2006");

            set1.Tables[1].Rows.Add("c1", "159", "10/12/2006");

            set1.Tables[1].Rows.Add("c2", "169", "10/10/2006");

            set1.Tables[1].Rows.Add("c2", "179", "10/10/2006");

            set1.Tables[1].Rows.Add("c2", "189", "10/12/2006");

            set1.Tables[1].Rows.Add("c3", "122", "10/04/2006");

            set1.Tables[1].Rows.Add("c4", "130", "10/10/2006");

            set1.Tables[1].Rows.Add("c5", "1.29", "10/14/2006");

 

            DataRelation dr = new DataRelation("custOrders",

                set1.Tables["Customers"].Columns["CustomerID"],

                set1.Tables["Orders"].Columns["CustomerID"]);

            set1.Relations.Add(dr);

            return set1;

        }

        private int cachedPosition = -1;

        private bool cacheChildPosition = true;

 

        void relatedCM_ListChanged(object sender,

          ListChangedEventArgs e)

        {

            // Check to see if this is a caching situation.

            if (cacheChildPosition && cachePositionCheckBox.Checked)

            {

                // If so, check to see if it is a reset situation,

               //and the current position is greater than zero.

                CurrencyManager relatedCM = sender as

                     CurrencyManager;

                if (e.ListChangedType == ListChangedType.Reset &&

                  relatedCM.Position > 0)

 

                    // If so, cache the position of the child table.

                    cachedPosition = relatedCM.Position;

            }

        }

        void bindingSource1_CurrentChanged(object sender,

          EventArgs e)

        {

            // If the CurrentChanged event occurs, this is not a

            // caching situation.

            cacheChildPosition = false;

        }

        void relatedCM_PositionChanged(object sender, EventArgs e)

        {

            // Check to see if this is a caching situation.

            if (cacheChildPosition && cachePositionCheckBox.Checked)

            {

                CurrencyManager relatedCM =

                  sender as CurrencyManager;

 

                // If so, check to see if the current position is

                // not equal to the cached position and the cached

                // position is not out of bounds.

                if (relatedCM.Position != cachedPosition &&

                   cachedPosition > 0 && cachedPosition

                   < relatedCM.Count)

                {

                    relatedCM.Position = cachedPosition;

                    cachedPosition = -1;

                }

            }

        }

 

        private void button1_Click(object sender, EventArgs e)

        {

            // For demo purposes--modifies a value in the first row

            // of the parent table.

            DataRow row1 = set1.Tables[0].Rows[0];

            row1[1] = DBNull.Value;

        }

       

        [STAThread]

        static void Main()

        {

            Application.EnableVisualStyles();

            Application.SetCompatibleTextRenderingDefault(false);

            Application.Run(new Form1());

        }

    }

 

   

}

Posted by cherylws | 1 Comments
Filed under:
More Posts Next page »
 
Page view tracker