Screen scaffolding: Special Filter/Find Controls

From OpenPetra Wiki
Jump to navigation Jump to search

This Page is still Work In Progress

Alan is still updating this page

Overview

A Filter/Find panel is part of many of the screens in Open Petra. It is a user control that is docked to the left of the screen beside the Grid that shows the data table content. The user control contains all the base functionality of creating its content and responding to user events. You configure the content of the user control by means of the YAML file for the screen. In many cases there will be no need for any manual code because all the code for Filter/Find can be auto-generated. However if you need to handle additional events or construct special controls, some manual code will be necessary.

The Filter/Find panel consists of one or two tabs - a Filter tab and/or a Find tab. You can have both or one without the other. The Filter tab allows the user to filter the rows in the grid by some combination of column values. The Find tab allows the user to skip from one row to the next that matches a given set of one or more column values. The Find tab is simpler to describe because it has fewer features than the Filter tab.

The Filter/Find panel is normally initially hidden by virtue of being collapsed to zero-width on the left side of the grid. Furthermore the controls on the panel are dynamically created the first time that the panel is shown. So there is no time penalty in terms of loading a screen that has a Filter/Find panel. When the user clicks the Filter button on the Buttons panel the user control is displayed for the first time. Likewise the panel can be collapsed by clicking the Filter button again or by clicking on the close box of the panel itself. This typical behaviour can be over-ridden by specifying that the panel is to be shown as soon as the screen loads.

Normally when the panel is collapsed (hidden) the filter is not applied - in other words when the filter button is clicked to close the panel the grid reverts to showing all rows. However this behaviour can be over-ridden as well so that the filter remains active when the panel is hidden.

The Filter panel can have one or two sets of column filter controls. This permits the developer to configure one set to be always on and the other set to be on when the panel is shown and off when the panel is hidden. The first set is referred to as the standard filter and the second set as the extra filter. The most common implementations will not need an extra filter.

Coding the YAML File

The special panel - pnlFilterAndFind

If you want your screen to include a Filter/Find panel you simply include pnlFilterAndFind as the first control on the pnlGrid. Here is a typical example.

   Controls:
       pnlContent:
           Controls: [pnlGrid, pnlDetails]
           Dock: Fill
       pnlGrid:
           Dock: Fill
           Controls: [pnlFilterAndFind, pnlButtons, grdDetails]
       pnlFilterAndFind:
           ExpandedWidth: 175
           FilterControls: [txtDetailBusinessCode, txtDetailBusinessDescription, chkDetailDeletable]
           FindControls: [txtDetailBusinessCode, txtDetailBusinessDescription, chkDetailDeletable]
       pnlButtons:
           Dock: Bottom
           Controls: [btnNew, btnDelete]
           ControlsOrientation: horizontal
       btnNew:
           Action: actNew
           Width: 80
       btnDelete:
           Action: actDelete
           Width: 80
       grdDetails:
           Dock: Fill
           Columns: [DetailBusinessCode, DetailBusinessDescription, DetailDeletable]
           ActionFocusRow: FocusedRowChanged
       pnlDetails:
           Dock: Bottom
           Controls:
               Row0: [txtDetailBusinessCode, txtDetailBusinessDescription]
               Row1: [chkDetailDeletable]

This screen has a content panel consisting of a grid panel and a details panel. The grid panel has the Filter/Find panel, a buttons panel and the grid itself. The buttons panel has two buttons - New and Delete. The pnlGrid is set to Dock: Fill. The pnlButtons is set to Dock: Bottom and grdDetails is set to Dock: Fill.

The first important point to note is that there is no declaration of btnFilter. The code generation automatically includes both the filter button and the record counter on the right hand end of the buttons panel.

The pnlFilterAndFind element will typically have the following sub-elements.

  • ExpandedWidth: The width of the panel when it is not collapsed to the side. The default is 150 but you may need to increase this to 175 or 180.
  • InitiallyExpanded: Set this to true if you want the Filter/Find panel to be visible as soon as the screen loads. You should only do this if you have a filter component that is always on.
  • FilterControls: Usually you will include a list of control names from the details panel. You can also refer to a column name directly in the situation where there is no control for that column in the details panel. This is useful for columns that have internally generated values such as Batch Number. Finally there is the option to create a custom filter control from scratch. All these methods are described in more detail below.
  • ExtraFilterControls: Control definitions for the Extra Filter Panel. This is the optional additional panel that can have its own set of controls that apply in a different context from the Filter Panel.
  • FindControls: The control definitions for the Find Panel.

There are other elements and attributes that you might set and these are fully described below. However many screens will be similar to the code example above - only requiring three elements to obtain the full functionality.

Naming Conventions

There are a few caveats about the naming of controls (which apply throughout Open Petra). If you do not conform to these naming conventions the auto-generated code is unlikely to compile.

When you specify a control name such as txtDetailColumnName you are doing several things at once.

  • You are specifying that the control is a TextBox
  • You are setting the control's Name property.
  • You are specifying that the .NET version of the database column name is ColumnName - which implies that the underlying database column name is something like pt_column_name_c. This full name can be discovered from .NET code by invoking the method .GetColumnNameDBName()
  • You may or may not choose to include the Detail in txtDetailColumnName. So txtColumnName and txtDetailColumnName work the same.

So the control name definition specifies to the code generator important information about both the control and the database column to which it refers.

What does the Filter/Find Panel Generator Do?

When you specify a control in the details panel the generator creates a 'Shallow Clone' of the original. (You definitely do not want to reference the details panel control directly). In fact all the controls in the filter/find panels are always cloned from something. If there is no control in the details panel to clone from, the code will create an in-memory control and clone from that. In fact it makes a clone of both the control and its associated label. However the clone is not a full clone - for example, if the details control is disabled the new clone will always be enabled. If the details control is one of our special ComboBoxes containing items from a database list, the new clone will usually be a basic auto-complete ComboBox that accepts new values - but it will contain the same drop-down data as the original (but no side label).

If you specify a Filter/Find control by means of the table column name, the generator will create an in-memory label and control for you. The label text will be constructed from the column name and the control will either be a CheckBox if the data is a Boolean or a TextBox otherwise.

The generator will automatically hook up the change events for the Filter/Find control, so that as you type text into a control a new filter string can be constructed and applied dynamically as a RowFilter to the data set.

Filter/Find YAML Definition Reference

This section lists all the options available for the definition of the Filter/Find Panel.

Creating the Panel

You include pnlFilterAndFind as the first element inside pnlGrid. Then you declare a pnlFilterAndFind control in the root Controls element.

Controls in the Filter/Find panel are laid out vertically. Typically each control has a label above it and usually a Clear button beside it on the right. However check-boxes usually have no label but the label text is applied as the CheckBox text.

The Clear Button

By default all the controls that you clone will have a small Clear Button (marked with an X) on the right hand side of the control. The user can click this button to set the control to its cleared state, which implies 'Do not include this column in the overall filter'. Normally this is the correct behaviour. The cleared state of a TextBox or ComboBox is empty text. The cleared state of a CheckBox is the 'indeterminate' state. If a radio button group is cloned and the clone is to have a Clear button, the clone will have an additional option (All) automatically added unless it already has an All option. The cleared state is then the All button.

Elements and Attributes of pnlFilterAndFind

  • ExpandedWidth: The width of the Filter/Find panel when it is visible (expanded).
  • InitiallyExpanded: Set this to true if you want the panel visible when the screen loads.
  • ShowApplyFilterButton: The default is not to show the Apply Filter button but you can change this behaviour by setting one of the following contexts: FilterContext.StandardFilterOnly, FilterContext.ExtraFilterOnly, FilterContext.StandardAndExtraFilter (or FilterContext.None).
  • ShowKeepFilterTurnedOnButton: The default is not to show the Keep Filter Turned On button. Modify this behaviour by specifying the contexts in which the button should be shown (see the item above).
  • ShowFilterIsAlwaysOnLabel: If you need the filter to always be on and the user to not have the option to turn it off, then show the Always On label. As with the previous two items, specify the filter contexts in which this label is to be displayed.
  • FilterControls: A list of controls to be created on the (standard) Filter panel. See below for the options to specify the controls themselves.
  • ExtraFilterControls: A list of controls to be created on the Extra Filter panel. See below for the options to specify the controls themselves.
  • FindControls: A list of controls to be created on the Find panel. See below for the options to specify the controls themselves.
  • ControlAttributes: This element is used to specify additional attributes that apply to the cloned controls - for example to shorten the label text or to change the width if a cloned control compared to the cloned-from control.
  • Panels: You will need this element if you create a user defined set of Filter Panel controls.

Specifying Filter/Find Controls by Cloning from the Details Panel

To include filtering based on a details panel control simply add the details panel control name to the list of controls in the FilterControls or ExtraFilterControls or FindControls.

The generator will clone both the control and its label (if it has one). By default, check boxes will have no label but will have text for the box itself.

The generator will clone the following controls:

  • Label
  • TextBox
  • CheckBox
  • Panels or GroupBoxes containing any of the above or RadioButtons
  • Any type of ComboBox including auto-populated ones. See more information about cloning ComboBoxes here.

Any control that you clone automatically has the SuppressChangeDetection attribute set.

  • If you do not want the cloned control to have a Clear button then you should add the attribute ClearButton=false to the attribute list for the control beneath the ControlAttributes element.

Here is an example of a filter/find panel that is simply created by cloning existing control.

       pnlFilterAndFind:
           ExpandedWidth: 175
           FilterControls: [txtDetailFeeCode, txtDetailFeeDescription, cmbDetailCostCentreCode, cmbDetailAccountCode, cmbDetailDrAccountCode]
           FindControls: [txtDetailFeeCode, txtDetailFeeDescription]
           ControlAttributes:
               cmbDetailCostCentreCode: {Label=CR Cost Centre}
               cmbDetailAccountCode: {Label=CR Account}
               cmbDetailDrAccountCode: {Label=DR Account}

This is an example of a screen where the labels needed to be modified slightly to make their meaning clear. Most screens will have no need of a ControlAttributes section at all.

Specifying Filter/Find Controls from the Column Name

You can specify a control using this syntax:

 Column: [TableName.]ColumnName

where ColumnName is the .NET name for the column and TableName is the .NET name for the table (e.g. ACurrencyTable). Normally it is not necessary to specify the TableName - the code will use the DetailTable specified in the YAML file.

When you specify the control by means of the column name the generator will use the column name as the basis for the control name and label text. So, if the column name is ColumnName, the label will be Column Name. You can change the label, or indeed any other property of the label or control in the ControlAttributes element (see below).

Specifying Filter/Find Controls as a User-Defined Panel

This is the most complex method for creating Filter/Find controls and it will involve additional manual code, in particular to handle resulting events. But on some screens there is no alternative to a user-defined panel. You can have multiple user-defined panels if necessary. Here is an example of a user-defined panel from the UC_GLBatches.yaml file

       pnlFilterAndFind:
           ExpandedWidth: 180
           InitiallyExpanded: true     
           ShowApplyFilterButton: FilterContext.ExtraFilterOnly
           ShowFilterIsAlwaysOnLabel: FilterContext.StandardFilterOnly
           ShowKeepFilterTurnedOnButton: FilterContext.ExtraFilterOnly
           FilterControls: [pnlBatchFilter]
           ExtraFilterControls: [Column:BatchNumber, txtDetailBatchDescription, txtDetailBatchControlTotal]
           FindControls: [Column:BatchNumber, txtDetailBatchDescription]
           Panels:
               pnlBatchFilter:
                   Controls: [rgrShowBatches, cmbYearFilter, cmbPeriodFilter]
                   
                   rgrShowBatches: 
                       Label: Show batches for
                       ClearButton: false
                       OptionalValues: [Posting, =Editing, All]
                       
                   cmbYearFilter: {OnChange=RefreshPeriods, ClearButton=false, Width=100}
                   cmbPeriodFilter: {OnChange=RefreshFilter, ClearButton=false, Width=250}
                   rbtPosting: {OnChange=RefreshFilter}
                   rbtEditing: {OnChange=RefreshFilter}
                   rbtAll: {OnChange=RefreshFilter}


To use such a panel you simply add a Panel control that is not part of the main screen control list to the relevant list of controls. In the example above, this is pnlBatchFilter. Then you define pnlBatchFilter beneath the Panels: element.

Each special Panel needs

  • A Controls element containing a list of controls
  • An element for each of the specified controls. These controls will require attributes to set their properties and to hook up to events. See the standard properties in the next section.
    • You can define any of the cloneable controls listed above - a Panel or GroupBox, or a TextBox, CheckBox or ComboBox.

Notice in the above example that you can specify an attribute for a radio button by using its implied name - e.g. rbtPosting for an OptionalValue of Posting - even though you have not explicitly declared an rbtPosting control.

Standard Attributes that can be Applied to all Filter/Find Panels

These are the attributes that can be applied to all Filter/Find panels

  • Label - the text for the label
  • NoLabel - set this to true to have no label. There is no need to do this explicitly for CheckBoxes. If the cloned-from check box has a label and no Text, the label text is applied as the control text, but if you specify label and control text they will both be preserved.
  • Text - the text for a control - usually a CheckBox
  • Width - the width of the control. The width cannot end up larger than the ExpandedWidth property of the Filter/Find panel. If you set it to a larger number it will display with the maximum width possible.
  • ClearButton - set this to false if you do not want a Clear Button
  • ClearValue - set this to a non-standard value if you want the clear button to have a different behaviour from blanking text or setting check boxes to an indeterminate state.
  • OnChange - specify an event handler in the manual code to handle this event
  • OptionalValues - a list for each radio button in a group
  • In principle you can add any other property that is recognised by .NET
  • You do NOT need to specify a SuppressChangeDetection attribute for your controls. All Filter/Find panel controls always have this attribute set automatically.

You set these attributes for a particular control by writing an element for the control name either beneath a ControlAttributes: section when using a clone from the details panel or beneath the specific panel element when creating a dynamic panel. See the examples above.

Using ComboBoxes on the Filter/Find Panel

Open Petra uses a variety of ComboBox styles on its GUI screens.

  • TCmbAutoComplete is derived from a standard Windows ComboBox, but it adds the functionality to display the full text of an entry (and select it) as the user starts to type. This is a very nice capability. This style of ComboBox can also accept new values that are not included in the list. It supports a DataSource containing a ValueMember and a DisplayMember (so each item could for example have a numeric value and a corresponding non-numeric text display).
  • TCmbVersatile derives from TCmbAutoComplete. It adds up to 4 more columns so that it becomes possible to display a descriptive label along with the drop-down text and value options. Crucially this style does not allow new values although of course it can include a 'blank' entry.
  • TCmbLabelled is a user control that contains a TCmbVersatile box and additional controls to show label text etc.
  • TCmbAutoPopulated is a further derivative of TCmbLabelled so that it can be populated automatically by referring to a database specific table.

You can see that each style becomes progressively more complex and by the time we get to the auto-populated Combo there are many more features than we need for the Filter/Find panel.

For the Filter/Find panel we always use a TCmbAutoComplete for the following reasons

  • The auto-complete feature is nice
  • It accepts a 'blank' entry which has the meaning: 'Do not filter on this column'.
  • It accepts text that is the start of an entry or entries, or even text that is in the middle of an entry or entries. This means that, whereas an auto-completed entry might only return one row, a part entry can return several, depending on the list content.
  • It supports content that is either simple strings or content from a DataSource that contains value and display members.

In all cases where we are cloning from the details panel controls this gives a good solution. If you are using a ComboBox on a user-defined panel (such as the example above) you may have to write special code to populate the box or adapt some existing code that expects a different type of ComboBox.

Manual Code and the Filter/Find Panel

The simplest screens will need no manual code at all. Beyond that

  • you can take advantage of a few optional manual methods that the automatic code will call if they are present in the -Manual.cs file.
  • you can access the individual controls on the panel(s) and modify their properties directly.
  • you will need to implement event handlers for any OnChange attributes you have created for a user-defined panel.

New Form-Level Variables

A screen with a Filter/Find panel has several new Form-Level variables

  • FucoFilterAndFind: This is the filter/find panel object itself. In actual fact it will be rare for you to need direct access to this object because other variables are more relevant to the needs of the manual programmer.
  • FFilterAndFindParameters: this structure contains useful information about the initial set up of the panel, including its expanded width and the contexts in which the additional buttons are displayed.
  • FFilterPanelControls and FFindPanelControls: These two members hold references to all the labels and filter/find controls on the relevant panel. You use these form level variables to access a particular control by one of a variety of methods.

These four variables should give you access to everything you need to manually program the panel behaviour.

In addition there are two filter properties of FFilterPanelControls that you will need to set/use on complex screens with user-defined panels

  • BaseOnFilter as a string
  • BaseOffFilter as a string

If you have buttons on the screen that can affect the filter you need to set these properties so that the Filter panel can include your manual filter with the filter that the user is specifying through the dynamic panel.

Names of Controls

Internally all controls on the filter panel have a name ending with "_filter". All controls on the find panel have a name ending with "_find". This is to ensure that they are uniquely identified and are different from the original control they were cloned from. Even controls that are created for a user-defined panel are cloned from a prototype that you specified. When you want to locate a control on a filter or find panel you access it using its 'original' cloned-from name, so you never need to be aware of the internal name.

Methods to Work With a Panel Control

The FFilterPanelControls variable has two sub-members: FFilterPanelControls.FStandardFilterPanels and FFilterPanelControls.FExtraFilterPanels. FFindPanelControls has just one sub-member: FFindPanelControls.FFindPanels.

The FFilterPanelControls and FFindPanelControls classes both have methods like

  • FindControlByName(string AControlName) returns a Control that has the specified name
  • FindControlByClonedFrom(Control AFromControl) returns a control that was cloned from the specified control
  • FindPanelByColumnName(string AColumnName) returns the filter panel that relates to the specified column name. The column name is the full database column name (like a_currency_name_c). When you have the Panel object you have access to its individual controls.
  • FindPanelByClonedFrom(Control AFromControl) returns the panel that contains the control that was cloned from the specified control. When you have the Panel object you have access to its individual controls.

When you have a reference to the control you are interested in you can cast it to its actual type and work with its properties, methods or events.

The TIndividualFilterFindPanel class has these properties

  • PanelLabel as a Label (may be null)
  • PanelControl as a Control
  • DBColumnName as a string (may be null)
  • DBColumnDataType as a string (e.g. bit, integer, number, varchar) (may be null)
  • FilterComparison as a string (may be null)
  • HasClearButton as a Boolean

Examples

This example changes the label text to something shorter than the text that it was cloned from

 FFilterPanelControls.FindControlByClonedFrom(lblDetailEmailAddress).Text = "Email address";

This example sets the checked state of a radio button

 ((RadioButton)FFilterPanelControls.FindControlByName("rbtEditing")).Checked = true;

Manual Code Placeholders

Use one or more of the following placeholders for your manual code.

To add manual code after the automatic code has generated the Filter/Find panels from the YAML

 private void CreateFilterFindPanelsManual()

To add manual code in response to the filter panel being toggled on/off. The parameter value represents the new state of the panel.

 private void FilterToggledManual(bool AIsCollapsed)

To modify the filter immediately before it gets applied. The parameter contains the filter that is about to be applied.

 private void ApplyFilterManual(ref AFilterString)

To modify the behaviour of the Find. The parameter is the row to test for a match. Return true if you want the row to be selected.

 private bool IsMatchingRowManual(DataRow ARowToMatch)