📄 listviewfilter.cs
字号:
/// <summary>
/// Create a new filter entry for the given column. This
/// will replace/add a LVFFilter instance for the column
/// to the itm_filtrs array for use by FilterUpdate().
/// </summary>
/// <param name="c">Column number</param>
private void FilterBuild( int c )
{
// if this column exists in the filters array remove it
foreach( LVFFilter f in itm_filtrs )
if ( f.Column == c )
{
itm_filtrs.Remove( f );
break;
}
// get the filter text for this column from the header
string s = hdr_contrl.Filter[ c ].Trim();
// if there is any size to it then create a new filter
if ( s.Length > 0 )
{
// create a new filter object for this column
LVFFilter f = new LVFFilter();
f.Column = c;
f.Text = s;
f.Compare = LVFFilterType.Equal;
f.Type = hdr_contrl.DataType[ c ];
// check the first characters of the string to see
// if this is not a default equality comparison
switch ( s[ 0 ] )
{
case '=':
f.Text = s.Remove( 0, 1 );
break;
case '!':
f.Compare = LVFFilterType.NotEqual;
f.Text = s.Remove( 0, 1 );
break;
case '>':
if ( ( s.Length > 1 ) && ( s[ 1 ] == '=' ) )
{
f.Compare = LVFFilterType.GreaterEqual;
f.Text = s.Remove( 0, 2 );
}
else
{
f.Compare = LVFFilterType.Greater;
f.Text = s.Remove( 0, 1 );
}
break;
case '<':
if ( ( s.Length > 1 ) && ( s[ 1 ] == '=' ) )
{
f.Compare = LVFFilterType.LessEqual;
f.Text = s.Remove( 0, 2 );
}
else
{
f.Compare = LVFFilterType.Less;
f.Text = s.Remove( 0, 1 );
}
break;
}
// add this to the array of filters
itm_filtrs.Add( f );
}
}
/// <summary>
/// Check a compare result against a filter type. We use
/// the internal CompareData and ItemText methods to get
/// the correct text and perform the comparison using the
/// filter column datatype. The result from CompareData
/// is used to check against the Type of filtering applied.
/// The f (LVFFilter) has been prepared with all the data
/// about the filtering to be performed; column, text,
/// datatype, and filtering type by FilterBuild().
/// </summary>
/// <param name="r">Result -1/0/1</param>
/// <param name="f">LVFFilter</param>
/// <returns></returns>
private bool FilterCheck( ListViewItem i, LVFFilter f )
{
// get the result of the data type comparison
int r = ( flt_ignore )
? CompareData( ItemText( i, f.Column ).ToLower(), f.Text.ToLower(), f.Type, true )
: CompareData( ItemText( i, f.Column ), f.Text, f.Type, true );
// compare the result against the filter comparison type
// a true result means that it passed filtering and should
// be left in the list of filtered items.
switch ( f.Compare )
{
case LVFFilterType.Equal:
return ( r == 0 );
case LVFFilterType.NotEqual:
return ( r != 0 );
case LVFFilterType.Greater:
return ( r > 0 );
case LVFFilterType.GreaterEqual:
return ( r >= 0 );
case LVFFilterType.Less:
return ( r < 0 );
case LVFFilterType.LessEqual:
return ( r <= 0 );
default:
return ( r == 0 );
}
}
/// <summary>
/// Update the Items list since a filter has changed. This
/// is done whenever a filter text has changed, the filter
/// options change (case or datatype). This is how we remove
/// items from the ListView.Items[] and put them into a private
/// ArrayList of 'filtered' items. The items moved to the
/// itm_filtrd array will be put back when filters are changed
/// or disabled. There is a problem with this however. The
/// ListViewItemsCollection has a real hard time processing
/// many additions to the list. When a large number of items
/// are added back in, it takes a long time. If there is
/// another way to Add (I tried AddRange) ListViewItem(s) to
/// the collection that is faster, I would really like to know.
/// </summary>
private void FilterUpdate()
{
// halt all updates to the list while we are working
this.BeginUpdate();
// set a flag to say that no change has occurred
// so that we don't unnecessarily sort the items
bool c = false;
// if there are any filters applied, process them.
// Items removed are not put into the itm_filtrd
// list at this time since we would then have to
// walk them again in the second loop. instead,
// they are added to a holding array (itm_holder)
// which is later copied into the itm_filtrd list.
// NOTE: we walk the Items list first, then check
// each itm_filtrs against that one item. That is
// much faster than walking the Items list for each
// itm_filtrs entry, since there is normally only
// one or two columns filtered at any time.
if ( itm_filtrs.Count != 0 )
{
// we will use a listviewitem instance here
ListViewItem l;
// prepare the held out items array to contain items
// removed this pass so we don't check them twice
itm_holder.Clear();
// check each item in the Items list to see if it
// should be removed to the filtered list
for ( int i = 0; i < this.Items.Count; ++i )
{
// get the item to check against the filters
l = this.Items[ i ];
// any filter failure removes from it from the
// Items list and adds it to the holding list.
// NOTE: the index (i) is decremented when we
// remove an item since the list is compressed.
foreach( LVFFilter f in itm_filtrs )
if ( !FilterCheck( l, f ) )
{
itm_holder.Add( l );
this.Items.RemoveAt( i-- );
c = true;
break;
}
}
// check each item in the currently filtered array
// to see if it should be put back in the items now.
// NOTE: this is the slow part when many items need
// to be put back into the ListViewItemsCollection...
for ( int i = 0; i < itm_filtrd.Count; ++i )
{
// get the object as a listviewitem
l = (ListViewItem)itm_filtrd[ i ];
// set no filter failure flag this item
bool r = true;
// all filters must pass, stop at one failure
foreach( LVFFilter f in itm_filtrs )
if ( !FilterCheck( l, f ) )
{
r = false;
break;
}
// if we did not fail any filter put it back in Items
// and remove it from the filtered items: itm_filtrd.
// NOTE: the index (i) is decremented when we
// remove an item since the list is compressed.
if ( r )
{
this.Items.Add( l );
itm_filtrd.RemoveAt( i-- );
c = true;
}
}
// now add all the holder items into the filtered list
// and empty the held out items array to remove reference
itm_filtrd.AddRange( itm_holder );
itm_holder.Clear();
}
// no filters active put any filtered items back into
// the items list and empty the filtered items array
else
{
// set the do sort flag only if there are entries
c = ( itm_filtrd.Count > 0 );
// put all filtered items back and clear the list
foreach( ListViewItem l in itm_filtrd ) Items.Add( l );
itm_filtrd.Clear();
}
// resort the items if the item content has changed
if ( c ) this.Sort();
// ensure that updates are re-enabled
this.EndUpdate();
}
/// <summary>
/// MenuItem click event. Perform the requested menu action
/// and always force a filter rebuild since all these change
/// the filtering properties in some manner. The sender
/// identifies the specific menuitem clicked.
/// </summary>
/// <param name="sender">MenuItem</param>
/// <param name="e">Event</param>
private void MenuItemClick(object sender, System.EventArgs e)
{
// 'Clear filter', set the Header.Filter for the column
if ( sender == mnu_clearf )
hdr_contrl.Filter[ mnu_column ] = "";
// 'Ignore case', toggle the flag
else if ( sender == mnu_ignore )
flt_ignore = !flt_ignore;
// 'String', set the datatype comparison
else if ( sender == mnu_strflt )
hdr_contrl.DataType[ mnu_column ] = LVFDataType.String;
// 'Number', set the datatype comparison
else if ( sender == mnu_nbrflt )
hdr_contrl.DataType[ mnu_column ] = LVFDataType.Number;
// 'Date', set the datatype comparison
else if ( sender == mnu_datflt )
hdr_contrl.DataType[ mnu_column ] = LVFDataType.Date;
// 'Left', set the alignment
else if ( sender == mnu_alignl )
hdr_contrl.Alignment[ mnu_column ] = HorizontalAlignment.Left;
// 'Right', set the alignment
else if ( sender == mnu_alignr )
hdr_contrl.Alignment[ mnu_column ] = HorizontalAlignment.Right;
// 'Center', set the alignment
else if ( sender == mnu_alignc )
hdr_contrl.Alignment[ mnu_column ] = HorizontalAlignment.Center;
// unknown, ignore this type
else return;
// force a filter build on the specific column
FilterBuild( mnu_column );
// follow with a filter update
FilterUpdate();
// set focus to ourself after menu action otherwise
// focus is left in the filter edit itself...
this.Focus();
// if this was an alignment change then we need to invalidate
if ( ((MenuItem)sender).Parent == mnu_alignt ) this.Invalidate();
}
/// <summary>
/// Context menu popup event. Set context menu item states
/// for the current column data type and case sensitivity.
/// </summary>
/// <param name="sender">ContextMenu</param>
/// <param name="e">Event</param>
private void ContextMenuPopup(object sender, System.EventArgs e)
{
// set the correct radio menu item based upon the data type
// NOTE: this is bad, .NET should know how to treat radio
// items by using the WS_GROUP style in the control...
switch ( hdr_contrl.DataType[ mnu_column ] )
{
case LVFDataType.Date:
mnu_strflt.Checked = false;
mnu_nbrflt.Checked = false;
mnu_datflt.Checked = true;
break;
case LVFDataType.Number:
mnu_strflt.Checked = false;
mnu_nbrflt.Checked = true;
mnu_datflt.Checked = false;
break;
default:
mnu_strflt.Checked = true;
mnu_nbrflt.Checked = false;
mnu_datflt.Checked = false;
break;
}
// disable the checked DataType menu item
mnu_strflt.Enabled = !mnu_strflt.Checked;
mnu_nbrflt.Enabled = !mnu_nbrflt.Checked;
mnu_datflt.Enabled = !mnu_datflt.Checked;
// also set the current alignment
switch ( hdr_contrl.Alignment[ mnu_column ] )
{
case HorizontalAlignment.Center:
mnu_alignl.Checked = false;
mnu_alignr.Checked = false;
mnu_alignc.Checked = true;
break;
case HorizontalAlignment.Right:
mnu_alignl.Checked = false;
mnu_alignr.Checked = true;
mnu_alignc.Checked = false;
break;
default:
mnu_alignl.Checked = true;
mnu_alignr.Checked = false;
mnu_alignc.Checked = false;
break;
}
// disable the checked DataType menu item
mnu_alignl.Enabled = !mnu_alignl.Checked;
mnu_alignr.Enabled = !mnu_alignr.Checked;
mnu_alignc.Enabled = !mnu_alignc.Checked;
// set the checked state of the case insensitive
mnu_ignore.Checked = flt_ignore;
}
#endregion
#region Internal methods and data
/// <summary>
/// Return the Text of an item/subitem as a string. This is
/// done to simplify text retrieval in multiple places. It
/// would be nice if the ListViewItem itself had this...
/// </summary>
/// <param name="i">ListViewItem</param>
/// <param name="c">Column number</param>
/// <returns>Text or empty string</returns>
internal string ItemText( ListViewItem i, int c )
{
// ensure we have an item to work with here...
if ( i != null )
{
// if column 0 return the text
if ( c == 0 ) return i.Text;
// not 0, ensure that the subitem is valid and exists
if ( ( c < i.SubItems.Count ) && ( i.SubItems[ c ] != null ) )
return i.SubItems[ c ].Text;
}
// not valid item/subitem return empty string
return "";
}
/// <summary>
/// Compare two strings and return a -/=/+ result. This is
/// used for all filter checks and column sorting. The key
/// is that we also use the DataType to correctly compare.
/// </summary>
/// <param name="s1">First string</param>
/// <param name="s2">Second string</param>
/// <param name="type">DataType for comparison</param>
/// <returns>Less, Equal, Greater as -1,0,1</returns>
internal int CompareData( string s1, string s2, LVFDataType type, bool size )
{
// perform the requested datatype comparison. note that
// we put the float and datetime in a try catch
switch ( type )
{
// string comparison is easy. if the size is true, this
// is a filter comparison and only x characters count.
case LVFDataType.String:
if ( size && ( s1.Length > s2.Length ) )
return s1.Substring( 0, s2.Length ).CompareTo( s2 );
return s1.CompareTo( s2 );
// float requires parsing the data
case LVFDataType.Number:
try { cmp_float1 = float.Parse( s1 ); }
catch { cmp_float1 = float.MaxValue; };
try { cmp_float2 = float.Parse( s2 ); }
catch { cmp_float2 = float.MaxValue; };
return cmp_float1.CompareTo( cmp_float2 );
// date also requires a parse
case LVFDataType.Date:
try { cmp_datim1 = DateTime.Parse( s1 ); }
catch { cmp_datim1 = DateTime.MaxValue; };
try { cmp_datim2 = DateTime.Parse( s2 ); }
catch { cmp_datim2 = DateTime.MaxValue; };
return DateTime.Compare( cmp_datim1, cmp_datim2 );
// by default the strings are equal
default:
return 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -